summaryrefslogtreecommitdiff
path: root/lib/elixir/test/elixir/kernel/errors_test.exs
blob: 76df2621b62b77192b947eb4a463995909c56a2e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
Code.require_file("../test_helper.exs", __DIR__)

defmodule Kernel.ErrorsTest do
  use ExUnit.Case, async: true

  defmacro hello do
    quote location: :keep do
      def hello, do: :world
    end
  end

  test "no optional arguments in fn" do
    assert_eval_raise CompileError,
                      "nofile:1: anonymous functions cannot have optional arguments",
                      'fn x \\\\ 1 -> x end'

    assert_eval_raise CompileError,
                      "nofile:1: anonymous functions cannot have optional arguments",
                      'fn x, y \\\\ 1 -> x + y end'
  end

  test "invalid fn" do
    assert_eval_raise SyntaxError,
                      "nofile:1: expected anonymous functions to be defined with -> inside: 'fn'",
                      'fn 1 end'

    assert_eval_raise SyntaxError,
                      ~r"nofile:2: unexpected operator ->. If you want to define multiple clauses, ",
                      'fn 1\n2 -> 3 end'
  end

  test "invalid token" do
    assert_eval_raise SyntaxError,
                      "nofile:1: unexpected token: \"\u200B\" (column 7, codepoint U+200B)",
                      '[foo: \u200B]\noops'
  end

  test "reserved tokens" do
    assert_eval_raise SyntaxError, "nofile:1: reserved token: __aliases__", '__aliases__'
    assert_eval_raise SyntaxError, "nofile:1: reserved token: __block__", '__block__'
  end

  test "invalid __CALLER__" do
    assert_eval_raise CompileError,
                      "nofile:1: __CALLER__ is available only inside defmacro and defmacrop",
                      'defmodule Sample do def hello do __CALLER__ end end'
  end

  test "invalid __STACKTRACE__" do
    assert_eval_raise CompileError,
                      "nofile:1: __STACKTRACE__ is available only inside catch and rescue clauses of try expressions",
                      'defmodule Sample do def hello do __STACKTRACE__ end end'

    assert_eval_raise CompileError,
                      "nofile:1: __STACKTRACE__ is available only inside catch and rescue clauses of try expressions",
                      'defmodule Sample do try do raise "oops" rescue _ -> def hello do __STACKTRACE__ end end end'
  end

  test "invalid quoted token" do
    assert_eval_raise SyntaxError, "nofile:1: syntax error before: \"world\"", '"hello" "world"'

    assert_eval_raise SyntaxError, "nofile:1: syntax error before: 'Foobar'", '1 Foobar'

    assert_eval_raise SyntaxError, "nofile:1: syntax error before: foo", 'Foo.:foo'

    assert_eval_raise SyntaxError, "nofile:1: syntax error before: \"foo\"", 'Foo.:"foo\#{:bar}"'

    assert_eval_raise SyntaxError, "nofile:1: syntax error before: \"", 'Foo.:"\#{:bar}"'
  end

  test "invalid identifier" do
    message = fn name ->
      "nofile:1: invalid character \"@\" (codepoint U+0040) in identifier: #{name}"
    end

    assert_eval_raise SyntaxError, message.("foo@"), 'foo@'
    assert_eval_raise SyntaxError, message.("foo@"), 'foo@ '
    assert_eval_raise SyntaxError, message.("foo@bar"), 'foo@bar'

    message = fn name ->
      "nofile:1: invalid character \"@\" (codepoint U+0040) in alias: #{name}"
    end

    assert_eval_raise SyntaxError, message.("Foo@"), 'Foo@'
    assert_eval_raise SyntaxError, message.("Foo@bar"), 'Foo@bar'

    message = "nofile:1: invalid character \"!\" (codepoint U+0021) in alias: Foo!"
    assert_eval_raise SyntaxError, message, 'Foo!'

    message = "nofile:1: invalid character \"?\" (codepoint U+003F) in alias: Foo?"
    assert_eval_raise SyntaxError, message, 'Foo?'

    # TODO: Remove this check once we depend on OTP 20+
    if :erlang.system_info(:otp_release) >= '20' do
      message =
        "nofile:1: invalid character \"ó\" (codepoint U+00F3) in alias (only ascii characters are allowed): Foó"

      assert_eval_raise SyntaxError, message, 'Foó'

      message = ~r"""
      Elixir expects unquoted Unicode atoms and variables to be in NFC form.

      Got:

          "foó" \(codepoints 0066 006F 006F 0301\)

      Expected:

          "foó" \(codepoints 0066 006F 00F3\)

      """

      assert_eval_raise SyntaxError, message, :unicode.characters_to_nfd_list("foó")
    end
  end

  test "kw missing space" do
    msg = "nofile:1: keyword argument must be followed by space after: foo:"

    assert_eval_raise SyntaxError, msg, "foo:bar"
    assert_eval_raise SyntaxError, msg, "foo:+"
    assert_eval_raise SyntaxError, msg, "foo:+1"
  end

  test "invalid map start" do
    assert_eval_raise SyntaxError,
                      "nofile:1: expected %{ to define a map, got: %[",
                      "{:ok, %[], %{}}"
  end

  test "sigil terminator" do
    assert_eval_raise TokenMissingError,
                      "nofile:3: missing terminator: \" (for sigil ~r\" starting at line 1)",
                      '~r"foo\n\n'

    assert_eval_raise TokenMissingError,
                      "nofile:3: missing terminator: } (for sigil ~r{ starting at line 1)",
                      '~r{foo\n\n'
  end

  test "dot terminator" do
    assert_eval_raise TokenMissingError,
                      "nofile:1: missing terminator: \" (for function name starting at line 1)",
                      'foo."bar'
  end

  test "string terminator" do
    assert_eval_raise TokenMissingError,
                      "nofile:1: missing terminator: \" (for string starting at line 1)",
                      '"bar'
  end

  test "heredoc start" do
    assert_eval_raise SyntaxError,
                      "nofile:1: heredoc start must be followed by a new line after \"\"\"",
                      '"""bar\n"""'
  end

  test "heredoc terminator" do
    assert_eval_raise TokenMissingError,
                      "nofile:2: missing terminator: \"\"\" (for heredoc starting at line 1)",
                      '"""\nbar'

    assert_eval_raise SyntaxError,
                      "nofile:2: invalid location for heredoc terminator, please escape token or move it to its own line: \"\"\"",
                      '"""\nbar"""'
  end

  test "unexpected end" do
    assert_eval_raise SyntaxError, "nofile:1: unexpected token: end", '1 end'
  end

  test "syntax error" do
    assert_eval_raise SyntaxError, "nofile:1: syntax error before: '.'", '+.foo'
  end

  test "syntax error before sigil" do
    msg = fn x -> "nofile:1: syntax error before: sigil ~s starting with content '#{x}'" end

    assert_eval_raise SyntaxError, msg.("bar baz"), '~s(foo) ~s(bar baz)'
    assert_eval_raise SyntaxError, msg.(""), '~s(foo) ~s()'
    assert_eval_raise SyntaxError, msg.("bar "), '~s(foo) ~s(bar \#{:baz})'
    assert_eval_raise SyntaxError, msg.(""), '~s(foo) ~s(\#{:bar} baz)'
  end

  test "op ambiguity" do
    max = 1
    assert max == 1
    assert max(1, 2) == 2
  end

  test "syntax error with do" do
    assert_eval_raise SyntaxError, ~r/nofile:1: unexpected token "do"./, 'if true, do\n'

    assert_eval_raise SyntaxError, ~r/nofile:1: unexpected keyword "do:"./, 'if true do:\n'
  end

  test "syntax error on parens call" do
    msg =
      "nofile:1: unexpected parentheses. If you are making a function call, do not " <>
        "insert spaces between the function name and the opening parentheses. " <>
        "Syntax error before: '('"

    assert_eval_raise SyntaxError, msg, 'foo (hello, world)'
  end

  test "syntax error on nested no parens call" do
    msg = ~r"nofile:1: unexpected comma. Parentheses are required to solve ambiguity"

    assert_eval_raise SyntaxError, msg, '[foo 1, 2]'
    assert_eval_raise SyntaxError, msg, '[foo bar 1, 2]'
    assert_eval_raise SyntaxError, msg, '[do: foo 1, 2]'
    assert_eval_raise SyntaxError, msg, 'foo(do: bar 1, 2)'
    assert_eval_raise SyntaxError, msg, '{foo 1, 2}'
    assert_eval_raise SyntaxError, msg, '{foo bar 1, 2}'
    assert_eval_raise SyntaxError, msg, 'foo 1, foo 2, 3'
    assert_eval_raise SyntaxError, msg, 'foo 1, @bar 3, 4'
    assert_eval_raise SyntaxError, msg, 'foo 1, 2 + bar 3, 4'
    assert_eval_raise SyntaxError, msg, 'foo(1, foo 2, 3)'

    assert is_list(List.flatten([1]))
    assert is_list(Enum.reverse([3, 2, 1], [4, 5, 6]))
    assert is_list(Enum.reverse([3, 2, 1], [4, 5, 6]))
    assert false || is_list(Enum.reverse([3, 2, 1], [4, 5, 6]))
    assert [List.flatten(List.flatten([1]))] == [[1]]

    interpret = fn x -> Macro.to_string(Code.string_to_quoted!(x)) end
    assert interpret.("f 1 + g h 2, 3") == "f(1 + g(h(2, 3)))"

    assert interpret.("assert [] = TestRepo.all from p in Post, where: p.title in ^[]") ==
             "assert([] = TestRepo.all(from(p in Post, where: p.title() in ^[])))"
  end

  test "syntax error on atom dot alias" do
    msg =
      "nofile:1: atom cannot be followed by an alias. If the '.' was meant to be " <>
        "part of the atom's name, the atom name must be quoted. Syntax error before: '.'"

    assert_eval_raise SyntaxError, msg, ':foo.Bar'
    assert_eval_raise SyntaxError, msg, ':"foo".Bar'
  end

  test "syntax error with no token" do
    assert_eval_raise TokenMissingError,
                      "nofile:1: missing terminator: ) (for \"(\" starting at line 1)",
                      'case 1 ('
  end

  test "clause with defaults" do
    assert_eval_raise CompileError,
                      ~r"nofile:3: definitions with multiple clauses and default values require a header",
                      ~C'''
                      defmodule Kernel.ErrorsTest.ClauseWithDefaults1 do
                        def hello(_arg \\ 0), do: nil
                        def hello(_arg \\ 1), do: nil
                      end
                      '''

    assert_eval_raise CompileError, ~r"nofile:2: undefined function foo/0", ~C'''
    defmodule Kernel.ErrorsTest.ClauseWithDefaults3 do
      def hello(foo, bar \\ foo())
      def hello(foo, bar), do: foo + bar
    end
    '''
  end

  test "different defs with defaults" do
    assert_eval_raise CompileError, "nofile:3: def hello/3 defaults conflicts with hello/2", ~C'''
    defmodule Kernel.ErrorsTest.DifferentDefsWithDefaults1 do
      def hello(a, b \\ nil), do: a + b
      def hello(a, b \\ nil, c \\ nil), do: a + b + c
    end
    '''

    assert_eval_raise CompileError,
                      "nofile:3: def hello/2 conflicts with defaults from hello/3",
                      ~C'''
                      defmodule Kernel.ErrorsTest.DifferentDefsWithDefaults2 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_eval_raise CompileError, "nofile:2: undefined function bar/0", '''
    defmodule Kernel.ErrorsTest.BadForm do
      def foo, do: bar()
    end
    '''
  end

  test "literal on map and struct" do
    assert_eval_raise SyntaxError, "nofile:1: syntax error before: '}'", '%{{:a, :b}}'

    assert_eval_raise SyntaxError, "nofile:1: syntax error before: '{'", '%{:a, :b}{a: :b}'

    assert_eval_raise CompileError,
                      "nofile:1: expected key-value pairs in a map, got: put_in(foo.bar().baz(), nil)",
                      'foo = 1; %{put_in(foo.bar.baz, nil), :bar}'
  end

  test "struct fields on defstruct" do
    assert_eval_raise ArgumentError, "struct field names must be atoms, got: 1", '''
    defmodule Kernel.ErrorsTest.StructFieldsOnDefstruct do
      defstruct [1, 2, 3]
    end
    '''
  end

  test "struct access on body" do
    assert_eval_raise CompileError,
                      "nofile:3: cannot access struct Kernel.ErrorsTest.StructAccessOnBody, " <>
                        "the struct was not yet defined or the struct " <>
                        "is being accessed in the same context that defines it",
                      '''
                      defmodule Kernel.ErrorsTest.StructAccessOnBody do
                        defstruct %{name: "Brasilia"}
                        %Kernel.ErrorsTest.StructAccessOnBody{}
                      end
                      '''
  end

  test "struct errors" do
    assert_eval_raise CompileError,
                      "nofile:1: BadStruct.__struct__/1 is undefined, cannot expand struct BadStruct",
                      '%BadStruct{}'

    assert_eval_raise CompileError,
                      "nofile:1: BadStruct.__struct__/0 is undefined, cannot expand struct BadStruct",
                      '%BadStruct{} = %{}'

    defmodule BadStruct do
      def __struct__ do
        []
      end
    end

    assert_eval_raise CompileError,
                      "nofile:1: expected Kernel.ErrorsTest.BadStruct.__struct__/0 to return a map, got: []",
                      '%#{BadStruct}{} = %{}'

    defmodule GoodStruct do
      defstruct name: "john"
    end

    assert_eval_raise KeyError,
                      "key :age not found in: %Kernel.ErrorsTest.GoodStruct{name: \"john\"}",
                      '%#{GoodStruct}{age: 27}'

    assert_eval_raise CompileError,
                      "nofile:1: unknown key :age for struct Kernel.ErrorsTest.GoodStruct",
                      '%#{GoodStruct}{age: 27} = %{}'
  end

  test "name for defmodule" do
    assert_eval_raise CompileError, "nofile:1: invalid module name: 3", 'defmodule 1 + 2, do: 3'
  end

  test "@compile inline with undefined function" do
    assert_eval_raise CompileError,
                      "nofile:1: inlined function foo/1 undefined",
                      'defmodule Test do @compile {:inline, foo: 1} end'
  end

  test "invalid unquote" do
    assert_eval_raise CompileError, "nofile:1: unquote called outside quote", 'unquote 1'
  end

  test "invalid unquote splicing in oneliners" do
    assert_eval_raise ArgumentError,
                      "unquote_splicing only works inside arguments and block contexts, " <>
                        "wrap it in parens if you want it to work with one-liners",
                      '''
                      defmodule Kernel.ErrorsTest.InvalidUnquoteSplicingInOneliners do
                        defmacro oneliner2 do
                          quote do: unquote_splicing 1
                        end

                        def callme do
                          oneliner2
                        end
                      end
                      '''
  end

  test "undefined non-local function" do
    assert_eval_raise CompileError, "nofile:1: undefined function call/2", 'call foo, do: :foo'
  end

  test "invalid attribute" do
    msg = ~r"cannot inject attribute @foo into function/macro because cannot escape "

    assert_raise ArgumentError, msg, fn ->
      defmodule InvalidAttribute do
        @foo fn -> nil end
        def bar, do: @foo
      end
    end
  end

  test "invalid struct field value" do
    msg = ~r"invalid value for struct field baz, cannot escape "

    assert_raise ArgumentError, msg, fn ->
      defmodule InvaliadStructFieldValue do
        defstruct baz: fn -> nil end
      end
    end
  end

  test "match attribute in module" do
    msg = "invalid write attribute syntax, you probably meant to use: @foo expression"

    assert_raise ArgumentError, msg, fn ->
      defmodule MatchAttributeInModule do
        @foo = 42
      end
    end
  end

  test "invalid fn args" do
    assert_eval_raise TokenMissingError,
                      "nofile:1: missing terminator: end (for \"fn\" starting at line 1)",
                      'fn 1'
  end

  test "invalid escape" do
    assert_eval_raise TokenMissingError, "nofile:1: invalid escape \\ at end of file", '1 \\'
  end

  test "function local conflict" do
    assert_eval_raise CompileError,
                      "nofile:3: imported Kernel.&&/2 conflicts with local function",
                      '''
                      defmodule Kernel.ErrorsTest.FunctionLocalConflict do
                        def other, do: 1 && 2
                        def _ && _, do: :error
                      end
                      '''
  end

  test "macro local conflict" do
    assert_eval_raise CompileError,
                      "nofile:6: call to local macro &&/2 conflicts with imported Kernel.&&/2, " <>
                        "please rename the local macro or remove the conflicting import",
                      '''
                      defmodule Kernel.ErrorsTest.MacroLocalConflict 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_eval_raise UndefinedFunctionError,
                      "function Kernel.ErrorsTest.MacroWithUndefinedLocal.unknown/1" <>
                        " is undefined (function unknown/1 is not available)",
                      '''
                      defmodule Kernel.ErrorsTest.MacroWithUndefinedLocal do
                        defmacrop bar, do: unknown(1)
                        def baz, do: bar()
                      end
                      '''
  end

  test "private macro" do
    assert_eval_raise UndefinedFunctionError,
                      "function Kernel.ErrorsTest.PrivateMacro.foo/0 is undefined (function foo/0 is not available)",
                      '''
                      defmodule Kernel.ErrorsTest.PrivateMacro do
                        defmacrop foo, do: 1
                        defmacro bar, do: __MODULE__.foo
                        defmacro baz, do: bar()
                      end
                      '''
  end

  test "function definition with alias" do
    assert_eval_raise CompileError,
                      "nofile:2: function names should start with lowercase characters or underscore, invalid name Bar",
                      '''
                      defmodule Kernel.ErrorsTest.FunctionDefinitionWithAlias do
                        def Bar do
                          :baz
                        end
                      end
                      '''
  end

  test "function import conflict" do
    assert_eval_raise CompileError,
                      "nofile:3: function exit/1 imported from both :erlang and Kernel, call is ambiguous",
                      '''
                      defmodule Kernel.ErrorsTest.FunctionImportConflict do
                        import :erlang, warn: false
                        def foo, do: exit(:test)
                      end
                      '''
  end

  test "unrequired macro" do
    assert_eval_raise CompileError,
                      "nofile:2: you must require Kernel.ErrorsTest before invoking " <>
                        "the macro Kernel.ErrorsTest.hello/0",
                      '''
                      defmodule Kernel.ErrorsTest.UnrequiredMacro do
                        Kernel.ErrorsTest.hello()
                      end
                      '''
  end

  test "def defmacro clause change" do
    assert_eval_raise CompileError, "nofile:3: defmacro foo/1 already defined as def", '''
    defmodule Kernel.ErrorsTest.DefDefmacroClauseChange do
      def foo(1), do: 1
      defmacro foo(x), do: x
    end
    '''
  end

  test "def defp clause change from another file" do
    assert_eval_raise CompileError, ~r"nofile:4: def hello/0 already defined as defp", '''
    defmodule Kernel.ErrorsTest.DefDefmacroClauseChange do
      require Kernel.ErrorsTest
      defp hello, do: :world
      Kernel.ErrorsTest.hello()
    end
    '''
  end

  test "internal function overridden" do
    assert_eval_raise CompileError,
                      "nofile:2: cannot define def __info__/1 as it is automatically defined by Elixir",
                      '''
                      defmodule Kernel.ErrorsTest.InternalFunctionOverridden do
                        def __info__(_), do: []
                      end
                      '''
  end

  test "no macros" do
    assert_eval_raise CompileError, "nofile:2: could not load macros from module :lists", '''
    defmodule Kernel.ErrorsTest.NoMacros do
      import :lists, only: :macros
    end
    '''
  end

  test "invalid macro" do
    assert_eval_raise CompileError,
                      "nofile: invalid quoted expression: {:foo, :bar, :baz, :bat}",
                      '''
                      defmodule Kernel.ErrorsTest.InvalidMacro do
                        defmacrop oops do
                          {:foo, :bar, :baz, :bat}
                        end

                        def test, do: oops()
                      end
                      '''
  end

  test "unloaded module" do
    assert_eval_raise CompileError,
                      "nofile:1: module Certainly.Doesnt.Exist is not loaded and could not be found",
                      'import Certainly.Doesnt.Exist'
  end

  test "module imported from the context it was defined in" do
    assert_eval_raise CompileError,
                      ~r"nofile:4: module Kernel.ErrorsTest.ScheduledModule.Hygiene is not loaded but was defined.",
                      '''
                      defmodule Kernel.ErrorsTest.ScheduledModule do
                        defmodule Hygiene do
                        end
                        import Kernel.ErrorsTest.ScheduledModule.Hygiene
                      end
                      '''
  end

  test "module imported from the same module" do
    assert_eval_raise CompileError,
                      ~r"nofile:3: you are trying to use the module Kernel.ErrorsTest.ScheduledModule.Hygiene which is currently being defined",
                      '''
                      defmodule Kernel.ErrorsTest.ScheduledModule do
                        defmodule Hygiene do
                          import Kernel.ErrorsTest.ScheduledModule.Hygiene
                        end
                      end
                      '''
  end

  test "already compiled module" do
    assert_eval_raise ArgumentError,
                      "could not call eval_quoted with argument Record " <>
                        "because the module is already compiled",
                      'Module.eval_quoted Record, quote(do: 1), [], file: __ENV__.file'
  end

  test "@on_load attribute format" do
    assert_raise ArgumentError, ~r/should be an atom or a {atom, 0} tuple/, fn ->
      defmodule BadOnLoadAttribute do
        Module.put_attribute(__MODULE__, :on_load, "not an atom")
      end
    end
  end

  test "interpolation error" do
    assert_eval_raise SyntaxError,
                      "nofile:1: \"do\" is missing terminator \"end\". unexpected token: \")\" at line 1",
                      '"foo\#{case 1 do )}bar"'
  end

  test "in definition module" do
    assert_eval_raise CompileError,
                      "nofile:2: cannot define module Kernel.ErrorsTest.InDefinitionModule " <>
                        "because it is currently being defined in nofile:1",
                      '''
                      defmodule Kernel.ErrorsTest.InDefinitionModule do
                        defmodule Elixir.Kernel.ErrorsTest.InDefinitionModule, do: true
                      end
                      '''
  end

  test "invalid definition" do
    assert_eval_raise CompileError,
                      "nofile:1: invalid syntax in def 1.(hello)",
                      'defmodule Kernel.ErrorsTest.InvalidDefinition, do: (def 1.(hello), do: true)'
  end

  test "invalid size in bitstrings" do
    assert_eval_raise CompileError,
                      "nofile:1: cannot use ^x outside of match clauses",
                      'x = 8; <<a, b::size(^x)>> = <<?a, ?b>>'
  end

  test "end of expression" do
    # All valid examples
    Code.eval_quoted('''
    1;
    2;
    3

    (;)
    (;1)
    (1;)
    (1; 2)

    fn -> 1; 2 end
    fn -> ; end

    if true do
      ;
    end

    try do
      ;
    catch
      _, _ -> ;
    after
      ;
    end
    ''')

    # All invalid examples
    assert_eval_raise SyntaxError, "nofile:1: syntax error before: ';'", '1+;\n2'

    assert_eval_raise SyntaxError, "nofile:1: syntax error before: ';'", 'max(1, ;2)'
  end

  test "new line error" do
    assert_eval_raise SyntaxError,
                      "nofile:3: unexpectedly reached end of line. The current expression is invalid or incomplete",
                      'if true do\n  foo = [],\n  baz\nend'
  end

  test "characters literal are printed correctly in syntax errors" do
    assert_eval_raise SyntaxError, "nofile:1: syntax error before: ?a", ':ok ?a'
    assert_eval_raise SyntaxError, "nofile:1: syntax error before: ?\\s", ':ok ?\\s'
    assert_eval_raise SyntaxError, "nofile:1: syntax error before: ?す", ':ok ?す'
  end

  test "invalid \"fn do expr end\"" do
    assert_eval_raise SyntaxError,
                      "nofile:1: unexpected token \"do\". Anonymous functions are written as:\n\n" <>
                        "    fn pattern -> expression end\n\n" <> "Syntax error before: do",
                      'fn do :ok end'
  end

  test "bodyless function with guard" do
    assert_eval_raise CompileError, "nofile:2: missing :do option in \"def\"", '''
    defmodule Kernel.ErrorsTest.BodyessFunctionWithGuard do
      def foo(n) when is_number(n)
    end
    '''

    assert_eval_raise CompileError, "nofile:2: missing :do option in \"def\"", '''
    defmodule Kernel.ErrorsTest.BodyessFunctionWithGuard do
      def foo(n) when is_number(n), true
    end
    '''
  end

  test "invalid args for bodyless clause" do
    assert_eval_raise CompileError,
                      ~r"nofile:2: only variables and \\\\ are allowed as arguments in definition header.",
                      '''
                      defmodule Kernel.ErrorsTest.InvalidArgsForBodylessClause do
                        def foo(nil)
                        def foo(_), do: :ok
                      end
                      '''
  end

  test "bad multi-call" do
    assert_eval_raise CompileError,
                      "nofile:1: invalid argument for alias, expected a compile time atom or alias, got: 42",
                      'alias IO.{ANSI, 42}'

    assert_eval_raise CompileError,
                      "nofile:1: :as option is not supported by multi-alias call",
                      'alias Elixir.{Map}, as: Dict'

    assert_eval_raise UndefinedFunctionError,
                      "function List.\"{}\"/1 is undefined or private",
                      '[List.{Chars}, "one"]'
  end

  test "macros error stacktrace" do
    assert [
             {:erlang, :+, [1, :foo], _},
             {Kernel.ErrorsTest.MacrosErrorStacktrace, :sample, 1, _} | _
           ] =
             rescue_stacktrace("""
             defmodule Kernel.ErrorsTest.MacrosErrorStacktrace 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 Kernel.ErrorsTest.MacrosFunctionClauseStacktrace do
               import Kernel.ErrorsTest
               sample(1)
             end
             """)
  end

  test "macros interpreted function clause stacktrace" do
    assert [{Kernel.ErrorsTest.MacrosInterpretedFunctionClauseStacktrace, :sample, 1, _} | _] =
             rescue_stacktrace("""
             defmodule Kernel.ErrorsTest.MacrosInterpretedFunctionClauseStacktrace do
               defmacro sample(0), do: 0
               def other, do: sample(1)
             end
             """)
  end

  test "macros compiled callback" do
    assert [{Kernel.ErrorsTest, :__before_compile__, [env], _} | _] =
             rescue_stacktrace("""
             defmodule Kernel.ErrorsTest.MacrosCompiledCallback do
               Module.put_attribute(__MODULE__, :before_compile, Kernel.ErrorsTest)
             end
             """)

    assert %Macro.Env{module: Kernel.ErrorsTest.MacrosCompiledCallback} = env
  end

  test "failed remote call stacktrace includes file/line info" do
    try do
      bad_remote_call(1)
    rescue
      ArgumentError ->
        stack = System.stacktrace()

        assert [
                 {:erlang, :apply, [1, :foo, []], []},
                 {__MODULE__, :bad_remote_call, 1, [file: _, line: _]} | _
               ] = stack
    end
  end

  test "def fails when rescue, else or catch don't have clauses" do
    assert_eval_raise CompileError, ~r"expected -> clauses for :else in \"def\"", """
    defmodule Example do
      def foo do
        bar()
      else
        baz()
      end
    end
    """
  end

  defp bad_remote_call(x), do: x.foo

  defmacro sample(0), do: 0

  defmacro before_compile(_) do
    quote(do: _)
  end

  ## Helpers

  defp assert_eval_raise(given_exception, given_message, string) do
    assert_raise given_exception, given_message, fn ->
      Code.eval_string(string)
    end
  end

  defp rescue_stacktrace(string) do
    stacktrace =
      try do
        Code.eval_string(string)
        nil
      rescue
        _ -> System.stacktrace()
      end

    stacktrace || flunk("Expected expression to fail")
  end
end