Code.require_file "../test_helper.exs", __DIR__ defmodule Kernel.ErrorsTest do use ExUnit.Case, async: true import CompileAssertion defmodule UnproperMacro do defmacro unproper(args), do: args defmacro exit(args), do: args end defrecord Config, integer: 0 test :invalid_token do assert_compile_fail SyntaxError, "nofile:1: invalid token: \end", '\end\nlol\nbarbecue' end test :sigil_terminator do assert_compile_fail TokenMissingError, "nofile:3: missing terminator: \" (for sigil %r\" starting at line 1)", '%r"foo\n\n' assert_compile_fail TokenMissingError, "nofile:3: missing terminator: } (for sigil %r{ starting at line 1)", '%r{foo\n\n' assert_compile_fail TokenMissingError, "nofile:3: missing terminator: ) (for sigil %r( starting at line 1)", '%r(f(oo)\n\n' end test :dot_terminator do assert_compile_fail TokenMissingError, "nofile:1: missing terminator: \" (for function name starting at line 1)", 'foo."bar' end test :string_terminator do assert_compile_fail TokenMissingError, "nofile:1: missing terminator: \" (for string starting at line 1)", '"bar' end test :heredoc_terminator do assert_compile_fail TokenMissingError, "nofile:2: missing terminator: \"\"\" (for heredoc starting at line 1)", '"""\nbar' end test :unexpected_end do assert_compile_fail SyntaxError, "nofile:1: unexpected token: end", '1 end' end test :syntax_error do assert_compile_fail SyntaxError, "nofile:1: syntax error before: '.'", '+.foo' end test :syntax_error_on_op_ambiguity do msg = "nofile:1: \"a -1\" looks like a function call but there is a variable named \"a\", " <> "please use explicit parenthesis or even spaces" assert_compile_fail SyntaxError, msg, 'a = 1; a -1' max = 1 assert max == 1 assert (max 1, 2) == 2 end test :syntax_error_on_parens_call do msg = "nofile:1: unexpected parenthesis. If you are making a function call, do not " <> "insert spaces in between the function name and the opening parentheses. " <> "Syntax error before: '('" assert_compile_fail SyntaxError, msg, 'foo (hello, world)' assert_compile_fail SyntaxError, msg, 'foo ()' assert_compile_fail SyntaxError, msg, 'foo (), 1' end test :syntax_error_on_nested_no_parens_call do msg = "nofile:1: unexpected comma. Parentheses are required to solve ambiguity in " <> "nested calls. Syntax error before: ','" assert_compile_fail SyntaxError, msg, '[foo 1, 2]' assert_compile_fail SyntaxError, msg, '[do: foo 1, 2]' assert_compile_fail SyntaxError, msg, 'foo(do: bar 1, 2)' assert_compile_fail SyntaxError, msg, '{foo 1, 2}' assert_compile_fail SyntaxError, msg, 'foo 1, foo 2, 3' assert_compile_fail SyntaxError, msg, 'foo(1, foo 2, 3)' assert is_list [] assert is_list do: 1 assert is_list List.flatten [1] assert is_atom is_record 1..3, Range assert is_atom(is_record 1..3, Range) end test :syntax_error_with_no_token do assert_compile_fail TokenMissingError, "nofile:1: missing terminator: ) (for \"(\" starting at line 1)", 'case 1 (' end test :clause_with_defaults do assert_compile_fail CompileError, "nofile:3: def hello/1 has default values and multiple clauses, " <> "use a separate clause for declaring defaults", ''' defmodule ErrorsTest do def hello(arg // 0), do: nil def hello(arg // 1), do: nil end ''' end test :invalid_match_pattern do assert_compile_fail CompileError, "nofile:2: invalid pattern in match clause", ''' case true do true && true -> true end ''' end test :different_defs_with_defaults do assert_compile_fail CompileError, "nofile:3: def hello/3 defaults conflicts with def hello/2", ''' defmodule ErrorsTest do def hello(a, b // nil), do: a + b def hello(a, b // nil, c // nil), do: a + b + c end ''' assert_compile_fail CompileError, "nofile:3: def hello/2 conflicts with defaults from def hello/3", ''' defmodule ErrorsTest do def hello(a, b // nil, c // nil), do: a + b + c def hello(a, b // nil), do: a + b end ''' end test :bad_form do assert_compile_fail CompileError, "nofile:2: function bar/0 undefined", ''' defmodule ErrorsTest do def foo, do: bar end ''' end test :unbound_var do assert_compile_fail CompileError, "nofile:1: unbound variable ^x", '^x = 1' end test :unbound_not_match do assert_compile_fail CompileError, "nofile:1: cannot use ^x outside of match clauses", '^x' end test :unbound_expr do assert_compile_fail SyntaxError, "nofile:1: the unary operator ^ can only be used with variables, invalid expression ^x(1)", '^x(1)' end test :name_for_defmodule do assert_compile_fail CompileError, "nofile:1: invalid module name: 3", 'defmodule 1 + 2, do: 3' end test :invalid_scope_for_function do assert_compile_fail SyntaxError, "nofile:1: cannot invoke def outside module", 'def Foo, do: 2' assert_compile_fail SyntaxError, "nofile:3: cannot invoke defmacro outside module", '\n\ndefmacro Foo, do: 2' end test :invalid_unquote do assert_compile_fail CompileError, "nofile:1: unquote called outside quote", 'unquote 1' end test :invalid_quote_args do assert_compile_fail SyntaxError, "nofile:1: invalid args for quote", 'quote 1' end test :invalid_calls do assert_compile_fail SyntaxError, "nofile:1: invalid call foo(1)(2)", 'foo(1)(2)' assert_compile_fail SyntaxError, "nofile:1: invalid remote call on 1", '1.foo' end test :unhandled_stab do assert_compile_fail SyntaxError, "nofile:3: unhandled operator ->", ''' defmodule Mod do def fun do casea foo, do: (bar -> baz) end end ''' end test :undefined_non_local_function do assert_compile_fail CompileError, "nofile:1: function casea/2 undefined", 'casea foo, do: (bar -> baz)' end test :invalid_fn_args do assert_compile_fail TokenMissingError, "nofile:1: missing terminator: end (for \"fn\" starting at line 1)", 'fn 1' end test :function_local_conflict do assert_compile_fail CompileError, "nofile:1: imported Kernel.&&/2 conflicts with local function", ''' defmodule ErrorsTest do 1 && 2 def _ && _, do: :error end ''' end test :macro_local_conflict do assert_compile_fail CompileError, "nofile:6: call to local macro &&/2 conflicts with imported Kernel.&&/2, " <> "please rename the local macro or remove the conflicting import", ''' defmodule ErrorsTest do def hello, do: 1 || 2 defmacro _ || _, do: :ok defmacro _ && _, do: :error def world, do: 1 && 2 end ''' end test :macro_with_undefined_local do assert_compile_fail UndefinedFunctionError, "undefined function: ErrorsTest.unknown/1", ''' defmodule ErrorsTest do defmacrop bar, do: unknown(1) def baz, do: bar() end ''' end test :private_macro do assert_compile_fail UndefinedFunctionError, "undefined function: ErrorsTest.foo/0", ''' defmodule ErrorsTest do defmacrop foo, do: 1 defmacro bar, do: __MODULE__.foo defmacro baz, do: bar end ''' end test :function_definition_with_alias do assert_compile_fail SyntaxError, "nofile:2: function names should start with lowercase characters or underscore, invalid name Bar", ''' defmodule ErrorsTest do def Bar do :baz end end ''' end test :function_import_conflict do assert_compile_fail CompileError, "nofile:3: function exit/1 imported from both :erlang and Kernel, call is ambiguous", ''' defmodule ErrorsTest do import :erlang def foo, do: exit(:test) end ''' end test :import_invalid_macro do assert_compile_fail CompileError, "nofile:1: cannot import Kernel.invalid/1 because it doesn't exist", 'import Kernel, only: [invalid: 1]' end test :unrequired_macro do assert_compile_fail SyntaxError, "nofile:2: you must require Kernel.ErrorsTest.UnproperMacro before invoking " <> "the macro Kernel.ErrorsTest.UnproperMacro.unproper/1 " ''' defmodule ErrorsTest do Kernel.ErrorsTest.UnproperMacro.unproper([]) end ''' end test :def_defmacro_clause_change do assert_compile_fail CompileError, "nofile:3: defmacro foo/1 already defined as def", ''' defmodule ErrorsTest do def foo(1), do: 1 defmacro foo(x), do: x end ''' end test :internal_function_overridden do assert_compile_fail CompileError, "nofile:1: function __info__/1 is internal and should not be overridden", ''' defmodule ErrorsTest do def __info__(_), do: [] end ''' end test :no_macros do assert_compile_fail CompileError, "nofile:2: could not load macros from module :lists", ''' defmodule ErrorsTest do import :lists, only: :macros end ''' end test :invalid_macro do assert_compile_fail CompileError, "nofile: tuples in quoted expressions must have 2 or 3 items, invalid quoted expression: {:foo, :bar, :baz, :bat}", ''' defmodule ErrorsTest do defmacrop oops do { :foo, :bar, :baz, :bat } end def test, do: oops end ''' end test :unloaded_module do assert_compile_fail CompileError, "nofile:1: module Certainly.Doesnt.Exist is not loaded and could not be found", 'import Certainly.Doesnt.Exist' end test :scheduled_module do assert_compile_fail CompileError, "nofile:4: module ErrorsTest.Hygiene is not loaded but was defined. " <> "This happens because you are trying to use a module in the same context it is defined. " <> "Try defining the module outside the context that requires it.", ''' defmodule ErrorsTest do defmodule Hygiene do end import ErrorsTest.Hygiene end ''' end test :already_compiled_module do assert_compile_fail ArgumentError, "could not call eval_quoted on module Record " <> "because it was already compiled", 'Module.eval_quoted Record, quote(do: 1), [], file: __FILE__' end test :interpolation_error do assert_compile_fail SyntaxError, "nofile:1: \"do\" starting at line 1 is missing terminator \"end\". Unexpected token: )", '"foo\#{case 1 do )}bar"' end test :in_definition_module do assert_compile_fail CompileError, "nofile:1: cannot define module ErrorsTest because it is currently being defined", 'defmodule ErrorsTest, do: (defmodule Elixir.ErrorsTest, do: true)' end test :invalid_definition do assert_compile_fail SyntaxError, "nofile:1: invalid syntax in def 1.(hello)", 'defmodule ErrorsTest, do: (def 1.(hello), do: true)' end test :duplicated_bitstring_size do assert_compile_fail SyntaxError, "nofile:1: duplicated size definition for bitstring", '<<1 :: [size(12), size(13)]>>' end test :invalid_bitstring_specified do assert_compile_fail SyntaxError, "nofile:1: unknown bitstring specifier :atom", '<<1 :: :atom>>' assert_compile_fail SyntaxError, "nofile:1: unknown bitstring specifier unknown", '<<1 :: unknown>>' assert_compile_fail SyntaxError, "nofile:1: unknown bitstring specifier another(12)", '<<1 :: another(12)>>' assert_compile_fail SyntaxError, "nofile:1: size in bitstring expects an integer or a variable as argument", '<<1 :: size(:a)>>' assert_compile_fail SyntaxError, "nofile:1: unit in bitstring expects an integer as argument", '<<1 :: unit(:x)>>' end test :invalid_var! do assert_compile_fail CompileError, "nofile:1: expected var!(x) to expand to an existing variable or be a part of a match", 'var!(x)' end test :invalid_alias do assert_compile_fail CompileError, "nofile:1: invalid :as for alias, nested alias Sample.Lists not allowed", 'alias :lists, as: Sample.Lists' end test :invalid_import_option do assert_compile_fail CompileError, "nofile:1: unsupported option :ops given to import", 'import :lists, [ops: 1]' end test :invalid_access_protocol_not_available do assert_compile_fail CompileError, "nofile:2: module Unknown is not loaded and could not be found", ''' defmodule ErrorsTest do def sample(Unknown[integer: 0]), do: true end ''' end test :invalid_access_protocol_not_alias do assert_raise ArgumentError, "dynamic access cannot be invoked inside match and guard clauses", fn -> defmodule ErrorsTest do def sample(config[integer: 0]), do: true end end end test :invalid_access_protocol_not_record do assert_raise ArgumentError, "cannot use module Kernel.ErrorsTest in access protocol because it does not export __record__/1", fn -> defmodule ErrorsTest do def sample(Kernel.ErrorsTest[integer: 0]), do: true end end end test :invalid_access_protocol_not_keywords do assert_raise ArgumentError, "expected contents inside brackets to be a keyword list, got: [0]", fn -> defmodule ErrorsTest do def sample(Kernel.ErrorsTest.Config[0]), do: true end end end test :invalid_access_protocol_invalid_keywords do assert_raise ArgumentError, "record Kernel.ErrorsTest.Config does not have the keys: [:foo]", fn -> defmodule ErrorsTest do def sample(Kernel.ErrorsTest.Config[foo: :bar]), do: true end end end test :invalid_access_protocol_invalid_keywords_outside_match do assert_compile_fail ArgumentError, "record Kernel.ErrorsTest.Config does not have the keys: [:foo]", 'Kernel.ErrorsTest.Config[foo: :bar]' end test :invalid_rescue_clause do assert_compile_fail SyntaxError, "nofile:4: invalid rescue clause. The clause should match on an alias, a variable or be in the `var in [alias]` format", 'try do\n1\nrescue\nUndefinedFunctionError[arity: 1] -> false\nend' end test :invalid_bc_return do assert_compile_fail SyntaxError, "nofile:1: a bit comprehension expects a bit string << >> to be returned", 'bc x inlist [1, 2, 3], do: x' end test :invalid_bc_inbits_gen do assert_compile_fail SyntaxError, "nofile:1: a bit comprehension expects a bit string << >> to be used in inbits generators", 'bc x inbits "123", do: <>' end test :unbound_cond do assert_compile_fail ArgumentError, "unbound variable _ inside cond. If you want the last clause to match, you probably meant to use true ->", 'cond do _ -> true end' end test :fun_different_arities do assert_compile_fail SyntaxError, "nofile:1: cannot mix clauses with different arities in function definition", 'fn x -> x; x, y -> x + y end' end test :new_line_error do assert_compile_fail SyntaxError, "nofile:3: syntax error before: newline", 'if true do\n foo = [],\n baz\nend' end test :invalid_var_or_function_on_guard do assert_compile_fail CompileError, "nofile:2: unknown variable something_that_does_not_exist or " <> "cannot invoke function something_that_does_not_exist/0 inside guard", ''' case [] do [] when something_that_does_not_exist == [] -> :ok end ''' end test :bodyless_function_with_guard do assert_compile_fail SyntaxError, "nofile:2: missing do keyword in def", ''' defmodule ErrorsTest do def foo(n) when is_number(n) end ''' end test :invalid_function_on_match do assert_compile_fail CompileError, "nofile:1: cannot invoke function something_that_does_not_exist/0 inside match", 'case [] do; something_that_does_not_exist() -> :ok; end' end test :invalid_remote_on_match do assert_compile_fail CompileError, "nofile:1: cannot invoke remote function Hello.something_that_does_not_exist/0 inside match", 'case [] do; Hello.something_that_does_not_exist() -> :ok; end' end test :invalid_remote_on_guard do assert_compile_fail CompileError, "nofile:1: cannot invoke remote function Hello.something_that_does_not_exist/0 inside guard", 'case [] do; [] when Hello.something_that_does_not_exist == [] -> :ok; end' end test :typespec_errors do assert_compile_fail CompileError, "nofile:2: type foo() undefined", ''' defmodule ErrorsTest do @type omg :: foo end ''' assert_compile_fail CompileError, "nofile:2: spec for undefined function ErrorsTest.omg/0", ''' defmodule ErrorsTest do @spec omg :: atom end ''' end test :bad_unquoting do assert_compile_fail SyntaxError, "nofile: expected a valid quoted expression, got: {Range, 1, 3}", ''' defmodule ErrorsTest do def range(unquote(1..3)), do: :ok end ''' end test :macros_error_stacktrace do assert [{:erlang, :+, [1, :foo], _}, {ErrorsTest, :sample, 1, _}|_] = rescue_stacktrace(""") defmodule ErrorsTest do defmacro sample(num), do: num + :foo def other, do: sample(1) end """ end test :macros_function_clause_stacktrace do assert [{__MODULE__, :sample, 1, _}|_] = rescue_stacktrace(""") defmodule ErrorsTest do import Kernel.ErrorsTest sample(1) end """ end test :macros_interpreted_function_clause_stacktrace do assert [{ErrorsTest, :sample, 1, _}|_] = rescue_stacktrace(""") defmodule ErrorsTest do defmacro sample(0), do: 0 def other, do: sample(1) end """ end test :macros_compiled_callback do assert [{Kernel.ErrorsTest, :__before_compile__, [Macro.Env[module: ErrorsTest]], _}|_] = rescue_stacktrace(""") defmodule ErrorsTest do Module.put_attribute(__MODULE__, :before_compile, Kernel.ErrorsTest) end """ end defmacro sample(0), do: 0 defmacro before_compile(_) do quote(do: _) end ## Helpers defp rescue_stacktrace(expr) do result = try do :elixir.eval(to_char_list(expr), []) nil rescue _ -> System.stacktrace end result || raise(ExUnit.AssertionError, message: "Expected function given to rescue_stacktrace to fail") end end