require "../../spec_helper"
require "../../support/env"

private def declare_class_var(container : ClassVarContainer, name, var_type : Type, annotations = nil)
  var = MetaTypeVar.new(name)
  var.owner = container
  var.type = var_type
  var.annotations = annotations
  var.bind_to var
  var.freeze_type = var_type
  container.class_vars[name] = var
end

private def exit_code_command(code)
  {% if flag?(:win32) %}
    %(cmd.exe /c "exit #{code}")
  {% else %}
    case code
    when 0
      "true"
    when 1
      "false"
    else
      "/bin/sh -c 'exit #{code}'"
    end
  {% end %}
end

private def shell_command(command)
  {% if flag?(:win32) %}
    "cmd.exe /c #{Process.quote(command)}"
  {% else %}
    "/bin/sh -c #{Process.quote(command)}"
  {% end %}
end

private def newline
  {% if flag?(:win32) %}
    "\r\n"
  {% else %}
    "\n"
  {% end %}
end

module Crystal
  describe Macro do
    describe "node methods" do
      describe "location" do
        location = Location.new("foo.cr", 1, 2)

        it "filename" do
          assert_macro "{{x.filename}}", %("foo.cr"), {x: "hello".string.tap { |n| n.location = location }}
        end

        it "line_number" do
          assert_macro "{{x.line_number}}", %(1), {x: "hello".string.tap { |n| n.location = location }}
        end

        it "column number" do
          assert_macro "{{x.column_number}}", %(2), {x: "hello".string.tap { |n| n.location = location }}
        end

        it "end line_number" do
          assert_macro "{{x.end_line_number}}", %(1), {x: "hello".string.tap { |n| n.end_location = location }}
        end

        it "end column number" do
          assert_macro "{{x.end_column_number}}", %(2), {x: "hello".string.tap { |n| n.end_location = location }}
        end
      end

      describe "stringify" do
        it "expands macro with stringify call on string" do
          assert_macro "{{x.stringify}}", "\"\\\"hello\\\"\"", {x: "hello".string}
        end

        it "expands macro with stringify call on symbol" do
          assert_macro "{{x.stringify}}", %(":hello"), {x: "hello".symbol}
        end

        it "expands macro with stringify call on call" do
          assert_macro "{{x.stringify}}", %("hello"), {x: "hello".call}
        end

        it "expands macro with stringify call on number" do
          assert_macro "{{x.stringify}}", %("1"), {x: 1.int32}
        end
      end

      describe "symbolize" do
        it "expands macro with symbolize call on string" do
          assert_macro "{{x.symbolize}}", ":\"\\\"hello\\\"\"", {x: "hello".string}
        end

        it "expands macro with symbolize call on symbol" do
          assert_macro "{{x.symbolize}}", ":\":hello\"", {x: "hello".symbol}
        end

        it "expands macro with symbolize call on id" do
          assert_macro "{{x.id.symbolize}}", ":hello", {x: "hello".string}
        end
      end

      describe "id" do
        it "expands macro with id call on string" do
          assert_macro "{{x.id}}", "hello", {x: "hello".string}
        end

        it "expands macro with id call on symbol" do
          assert_macro "{{x.id}}", "hello", {x: "hello".symbol}
        end

        it "expands macro with id call on char" do
          assert_macro "{{x.id}}", "є", {x: CharLiteral.new('є')}
        end

        it "expands macro with id call on call" do
          assert_macro "{{x.id}}", "hello", {x: "hello".call}
        end

        it "expands macro with id call on number" do
          assert_macro "{{x.id}}", %(1), {x: 1.int32}
        end

        it "expands macro with id call on path" do
          assert_macro "{{x.id}}", %(Foo), {x: Path.new("Foo")}
        end

        it "expands macro with id call on global path" do
          assert_macro "{{x.id}}", %(::Foo), {x: Path.new("Foo", global: true)}
        end
      end

      it "executes == on numbers (true)" do
        assert_macro "{%if 1 == 1%}hello{%else%}bye{%end%}", "hello"
      end

      it "executes == on numbers (false)" do
        assert_macro "{%if 1 == 2%}hello{%else%}bye{%end%}", "bye"
      end

      it "executes != on numbers (true)" do
        assert_macro "{%if 1 != 2%}hello{%else%}bye{%end%}", "hello"
      end

      it "executes != on numbers (false)" do
        assert_macro "{%if 1 != 1%}hello{%else%}bye{%end%}", "bye"
      end

      it "executes == on symbols (true) (#240)" do
        assert_macro "{{:foo == :foo}}", "true"
      end

      it "executes == on symbols (false) (#240)" do
        assert_macro "{{:foo == :bar}}", "false"
      end

      describe "class_name" do
        it "executes class_name" do
          assert_macro "{{:foo.class_name}}", "\"SymbolLiteral\""
        end

        it "executes class_name" do
          assert_macro "{{x.class_name}}", "\"MacroId\"", {x: MacroId.new("hello")}
        end

        it "executes class_name" do
          assert_macro "{{x.class_name}}", "\"StringLiteral\"", {x: "hello".string}
        end

        it "executes class_name" do
          assert_macro "{{x.class_name}}", "\"SymbolLiteral\"", {x: "hello".symbol}
        end

        it "executes class_name" do
          assert_macro "{{x.class_name}}", "\"NumberLiteral\"", {x: 1.int32}
        end

        it "executes class_name" do
          assert_macro "{{x.class_name}}", "\"ArrayLiteral\"", {x: ArrayLiteral.new([Path.new("Foo"), Path.new("Bar")] of ASTNode)}
        end
      end

      describe "#nil?" do
        it "NumberLiteral" do
          assert_macro "{{ 1.nil? }}", "false"
        end

        it "NilLiteral" do
          assert_macro "{{ nil.nil? }}", "true"
        end

        it "Nop" do
          assert_macro "{{ x.nil? }}", "true", {x: Nop.new}
        end
      end

      describe "#is_a?" do
        it "union argument" do
          assert_macro %({{ x.is_a?(NumberLiteral | StringLiteral) }}), "true", {x: 1.int32}
          assert_macro %({{ x.is_a?(NumberLiteral | StringLiteral) }}), "true", {x: "hello".string}
          assert_macro %({{ x.is_a?(NumberLiteral | StringLiteral) }}), "false", {x: "hello".symbol}

          assert_macro %({{ x.is_a?(NumberLiteral | StringLiteral | SymbolLiteral) }}), "true", {x: 1.int32}
          assert_macro %({{ x.is_a?(NumberLiteral | StringLiteral | SymbolLiteral) }}), "true", {x: "hello".string}
          assert_macro %({{ x.is_a?(NumberLiteral | StringLiteral | SymbolLiteral) }}), "true", {x: "hello".symbol}
          assert_macro %({{ x.is_a?(NumberLiteral | StringLiteral | SymbolLiteral) }}), "false", {x: "hello".call}
        end

        it "union argument, mergeable" do
          assert_macro %({{ x.is_a?(NumberLiteral | ASTNode) }}), "true", {x: 1.int32}
          assert_macro %({{ x.is_a?(NumberLiteral | ASTNode) }}), "true", {x: "hello".string}
        end

        it "union argument, duplicate type" do
          assert_macro %({{ x.is_a?(NumberLiteral | NumberLiteral) }}), "true", {x: 1.int32}
          assert_macro %({{ x.is_a?(NumberLiteral | NumberLiteral) }}), "false", {x: "hello".string}
        end

        it "union argument, contains NoReturn" do
          assert_macro %({{ x.is_a?(NumberLiteral | NoReturn) }}), "true", {x: 1.int32}
          assert_macro %({{ x.is_a?(NumberLiteral | NoReturn) }}), "false", {x: "hello".string}
        end

        it "union argument, undefined types" do
          assert_macro %({{ x.is_a?(NumberLiteral | String) }}), "true", {x: 1.int32}
          assert_macro %({{ x.is_a?(NumberLiteral | String) }}), "false", {x: "hello".string}
          assert_macro %({{ x.is_a?(Int32 | String) }}), "false", {x: 1.int32}
        end

        it "union argument, unimplemented types" do
          assert_macro %({{ x.is_a?(ClassDef) }}), "true", {x: ClassDef.new("Foo".path)}
          assert_macro %({{ x.is_a?(ModuleDef) }}), "false", {x: ClassDef.new("Foo".path)}
        end
      end

      describe "#doc" do
        it "returns an empty string if there are no docs on the node (wants_doc = false)" do
          assert_macro "{{ x.doc }}", %(""), {x: Call.new("some_call")}
        end

        it "returns the call's docs if present (wants_doc = true)" do
          assert_macro "{{ x.doc }}", %("Some docs"), {x: Call.new("some_call").tap { |c| c.doc = "Some docs" }}
        end

        it "returns a multiline comment" do
          assert_macro "{{ x.doc }}", %("Some\\nmulti\\nline\\ndocs"), {x: Call.new("some_call").tap { |c| c.doc = "Some\nmulti\nline\ndocs" }}
        end
      end

      describe "#doc_comment" do
        it "returns an empty MacroId if there are no docs on the node (wants_doc = false)" do
          assert_macro "{{ x.doc_comment }}", %(), {x: Call.new("some_call")}
        end

        it "returns the call's docs if present as a MacroId (wants_doc = true)" do
          assert_macro "{{ x.doc_comment }}", %(Some docs), {x: Call.new("some_call").tap { |c| c.doc = "Some docs" }}
        end

        it "ensures each newline has a `#` prefix" do
          assert_macro "{{ x.doc_comment }}", %(Some\n# multi\n# line\n# docs), {x: Call.new("some_call").tap { |c| c.doc = "Some\nmulti\nline\ndocs" }}
        end
      end
    end

    describe "number methods" do
      it "executes > (true)" do
        assert_macro "{%if 2 > 1%}hello{%else%}bye{%end%}", "hello"
      end

      it "executes > (false)" do
        assert_macro "{%if 2 > 3%}hello{%else%}bye{%end%}", "bye"
      end

      it "executes >= (true)" do
        assert_macro "{%if 1 >= 1%}hello{%else%}bye{%end%}", "hello"
      end

      it "executes >= (false)" do
        assert_macro "{%if 2 >= 3%}hello{%else%}bye{%end%}", "bye"
      end

      it "executes < (true)" do
        assert_macro "{%if 1 < 2%}hello{%else%}bye{%end%}", "hello"
      end

      it "executes < (false)" do
        assert_macro "{%if 3 < 2%}hello{%else%}bye{%end%}", "bye"
      end

      it "executes <= (true)" do
        assert_macro "{%if 1 <= 1%}hello{%else%}bye{%end%}", "hello"
      end

      it "executes <= (false)" do
        assert_macro "{%if 3 <= 2%}hello{%else%}bye{%end%}", "bye"
      end

      it "executes <=>" do
        assert_macro "{{1 <=> -1}}", "1"
      end

      it "executes <=> (returns nil)" do
        assert_macro "{{0.0/0.0 <=> -1}}", "nil"
      end

      it "executes +" do
        assert_macro "{{1 + 2}}", "3"
      end

      it "executes + and preserves type" do
        assert_macro "{{1_u64 + 2_u64}}", "3_u64"
      end

      it "executes -" do
        assert_macro "{{1 - 2}}", "-1"
      end

      it "executes *" do
        assert_macro "{{2 * 3}}", "6"
      end

      # MathInterpreter only works with Integer and left / right : Float
      #
      # it "executes /" do
      #   assert_macro "{{5 / 3}}", "1"
      # end

      it "executes //" do
        assert_macro "{{5 // 3}}", "1"
      end

      it "executes %" do
        assert_macro "{{5 % 3}}", "2"
      end

      it "preserves integer size (#10713)" do
        assert_macro "{{ 3000000000u64 % 2 }}", "0_u64"
      end

      it "executes &" do
        assert_macro "{{5 & 3}}", "1"
      end

      it "executes |" do
        assert_macro "{{5 | 3}}", "7"
      end

      it "executes ^" do
        assert_macro "{{5 ^ 3}}", "6"
      end

      it "executes **" do
        assert_macro "{{2 ** 3}}", "8"
      end

      it "executes <<" do
        assert_macro "{{1 << 2}}", "4"
      end

      it "executes >>" do
        assert_macro "{{4 >> 2}}", "1"
      end

      it "executes + with float" do
        assert_macro "{{1.5 + 2.6}}", "4.1"
      end

      it "executes unary +" do
        assert_macro "{{+3}}", "+3"
      end

      it "executes unary -" do
        assert_macro "{{-(3)}}", "-3"
        assert_macro "{{-(3_i128)}}", "-3_i128"
      end

      it "executes unary ~" do
        assert_macro "{{~1}}", "-2"
      end

      it "executes kind" do
        assert_macro "{{-128i8.kind}}", ":i8"
        assert_macro "{{1e-123_f32.kind}}", ":f32"
        assert_macro "{{1.0.kind}}", ":f64"
        assert_macro "{{0xde7ec7ab1e_u64.kind}}", ":u64"
        assert_macro "{{1_u128.kind}}", ":u128"
        assert_macro "{{-20i128.kind}}", ":i128"
      end

      it "#to_number" do
        assert_macro "{{ 4_u8.to_number }}", "4"
        assert_macro "{{ 2147483648.to_number }}", "2147483648"
        assert_macro "{{ 1_f32.to_number }}", "1.0"
        assert_macro "{{ 4_u128.to_number }}", "4"
        assert_macro "{{ -20i128.to_number }}", "-20"
      end

      it "executes math operations using U/Int128" do
        assert_macro "{{18446744073709551615_u128 + 1}}", "18446744073709551616_u128"
        assert_macro "{{18446744073709551_i128 - 1_u128}}", "18446744073709550_i128"
        assert_macro "{{18446744073709551615_u128 * 10}}", "184467440737095516150_u128"
        assert_macro "{{18446744073709551610_u128 // 10}}", "1844674407370955161_u128"
      end
    end

    describe "char methods" do
      it "executes ord" do
        assert_macro %({{'a'.ord}}), %(97)
        assert_macro %({{'龍'.ord}}), %(40845)
      end

      it "executes zero?" do
        assert_macro "{{0.zero?}}", "true"
        assert_macro "{{1.zero?}}", "false"

        assert_macro "{{0.0.zero?}}", "true"
        assert_macro "{{0.1.zero?}}", "false"
      end
    end

    describe "string methods" do
      it "executes string == string" do
        assert_macro %({{"foo" == "foo"}}), %(true)
        assert_macro %({{"foo" == "bar"}}), %(false)
      end

      it "executes string != string" do
        assert_macro %({{"foo" != "foo"}}), %(false)
        assert_macro %({{"foo" != "bar"}}), %(true)
      end

      it "executes string * number" do
        assert_macro %({{"odelay" * 3}}), "\"odelayodelayodelay\""
      end

      describe "#split" do
        it "works without arguments" do
          assert_macro %({{"1 2 3".split}}), %(["1", "2", "3"] of ::String)
        end

        it "works with string argument" do
          assert_macro %({{"1-2-3".split("-")}}), %(["1", "2", "3"] of ::String)
        end

        it "works with char argument" do
          assert_macro %({{"1-2-3".split('-')}}), %(["1", "2", "3"] of ::String)
        end

        it "works with regex argument" do
          assert_macro %({{"123-456-789".split(/-(.)/)}}), %(["123", "4", "56", "7", "89"] of ::String)
        end
      end

      it "executes strip" do
        assert_macro %({{"  hello   ".strip}}), %("hello")
      end

      it "executes downcase" do
        assert_macro %({{"HELLO".downcase}}), %("hello")
      end

      it "executes upcase" do
        assert_macro %({{"hello".upcase}}), %("HELLO")
      end

      it "executes capitalize" do
        assert_macro %({{"hello".capitalize}}), %("Hello")
      end

      it "executes chars" do
        assert_macro %({{x.chars}}), %(['1', '2', '3'] of ::Char), {x: StringLiteral.new("123")}
      end

      it "executes lines" do
        assert_macro %({{x.lines}}), %(["1", "2", "3"] of ::String), {x: StringLiteral.new("1\n2\n3")}
      end

      it "executes size" do
        assert_macro %({{"hello".size}}), "5"
      end

      it "executes count" do
        assert_macro %({{"aabbcc".count('a')}}), "2"
      end

      it "executes empty" do
        assert_macro %({{"hello".empty?}}), "false"
      end

      it "executes [] with inclusive range" do
        assert_macro %({{"hello"[1..-2]}}), %("ell")
      end

      it "executes [] with exclusive range" do
        assert_macro %({{"hello"[1...-2]}}), %("el")
      end

      it "executes [] with computed range" do
        assert_macro %({{"hello"[[1].size..-2]}}), %("ell")
      end

      it "executes [] with incomplete range" do
        assert_macro %({{"hello"[1..]}}), %("ello")
        assert_macro %({{"hello"[1..nil]}}), %("ello")
        assert_macro %({{"hello"[...3]}}), %("hel")
        assert_macro %({{"hello"[nil...3]}}), %("hel")
        assert_macro %({{"hello"[..]}}), %("hello")
        assert_macro %({{"hello"[nil..nil]}}), %("hello")
      end

      it "executes string chomp" do
        assert_macro %({{"hello\n".chomp}}), %("hello")
      end

      it "executes string starts_with? char (true)" do
        assert_macro %({{"hello".starts_with?('h')}}), %(true)
      end

      it "executes string starts_with? char (false)" do
        assert_macro %({{"hello".starts_with?('e')}}), %(false)
      end

      it "executes string starts_with? string (true)" do
        assert_macro %({{"hello".starts_with?("hel")}}), %(true)
      end

      it "executes string starts_with? string (false)" do
        assert_macro %({{"hello".starts_with?("hi")}}), %(false)
      end

      it "executes string ends_with? char (true)" do
        assert_macro %({{"hello".ends_with?('o')}}), %(true)
      end

      it "executes string ends_with? char (false)" do
        assert_macro %({{"hello".ends_with?('e')}}), %(false)
      end

      it "executes string ends_with? string (true)" do
        assert_macro %({{"hello".ends_with?("llo")}}), %(true)
      end

      it "executes string ends_with? string (false)" do
        assert_macro %({{"hello".ends_with?("tro")}}), %(false)
      end

      it "executes string + string" do
        assert_macro %({{"hello" + " world"}}), %("hello world")
      end

      it "executes string + char" do
        assert_macro %({{"hello" + 'w'}}), %("hellow")
      end

      it "executes string =~ (false)" do
        assert_macro %({{"hello" =~ /hei/}}), %(false)
      end

      it "executes string =~ (true)" do
        assert_macro %({{"hello" =~ /ell/}}), %(true)
      end

      it "executes string > string" do
        assert_macro %({{"fooa" > "foo"}}), %(true)
        assert_macro %({{"foo" > "fooa"}}), %(false)
      end

      it "executes string > macroid" do
        assert_macro %({{"fooa" > "foo".id}}), %(true)
        assert_macro %({{"foo" > "fooa".id}}), %(false)
      end

      it "executes string < string" do
        assert_macro %({{"fooa" < "foo"}}), %(false)
        assert_macro %({{"foo" < "fooa"}}), %(true)
      end

      it "executes string < macroid" do
        assert_macro %({{"fooa" < "foo".id}}), %(false)
        assert_macro %({{"foo" < "fooa".id}}), %(true)
      end

      it "executes tr" do
        assert_macro %({{"hello".tr("e", "o")}}), %("hollo")
      end

      it "executes gsub" do
        assert_macro %({{"hello".gsub(/e|o/, "a")}}), %("halla")
      end

      it "executes gsub with a block" do
        assert_macro %q({{ "foo bar baz".gsub(/ba./) { "biz" } }}), %("foo biz biz")                                                                  # No block args
        assert_macro %q({{ "foo bar baz".gsub(/ba./) { |match| match.upcase } }}), %("foo BAR BAZ")                                                   # full matched string
        assert_macro %q({{ "Name: Alice, Name: Bob".gsub(/Name: (\w+)/) { |full, matches| "User(#{matches[1].id})" } }}), %("User(Alice), User(Bob)") # single capture group
        assert_macro %q({{ "5x10, 3x7".gsub(/(\d+)x(\d+)/) { |full, matches| "#{matches[1].to_i * matches[2].to_i}" } }}), %("50, 21")                # multiple capture groups
        assert_macro %q({{ "bar baz".gsub /bar (foo)?/ { |_, matches| matches[1].nil? ? "" : "BUG" } }}), %("baz")                                    # Capture group no match
        assert_macro %q({{ "bar".gsub /(foo)/ { "STR" } }}), %("bar")                                                                                 # No match at all
      end

      it "executes match" do
        assert_macro %({{ "hello world".match(/x/) }}), %(nil)
        assert_macro %({{ "hello world".match(/o.*o/) }}), %({0 => "o wo"} of ::Int32 | ::String => ::String | ::Nil)
        assert_macro %({{ "hello world".match(/(?:(x)|e)(?<name>\\S+)/) }}), %({0 => "ello", 1 => nil, "name" => "llo"} of ::Int32 | ::String => ::String | ::Nil)
      end

      it "executes scan" do
        assert_macro %({{"Crystal".scan(/(Cr)(?<name1>y)(st)(?<name2>al)/)}}), %([{0 => "Crystal", 1 => "Cr", "name1" => "y", 3 => "st", "name2" => "al"} of ::Int32 | ::String => ::String | ::Nil] of ::Hash(::Int32 | ::String, ::String | ::Nil))
        assert_macro %({{"Crystal".scan(/(Cr)?(stal)/)}}), %([{0 => "stal", 1 => nil, 2 => "stal"} of ::Int32 | ::String => ::String | ::Nil] of ::Hash(::Int32 | ::String, ::String | ::Nil))
        assert_macro %({{"Ruby".scan(/Crystal/)}}), %([] of ::Hash(::Int32 | ::String, ::String | ::Nil))
      end

      it "executes camelcase" do
        assert_macro %({{"foo_bar".camelcase}}), %("FooBar")
      end

      it "executes camelcase with lower" do
        assert_macro %({{"foo_bar".camelcase(lower: true)}}), %("fooBar")
      end

      it "executes camelcase with invalid lower arg type" do
        assert_macro_error %({{"foo_bar".camelcase(lower: 99)}}), "named argument 'lower' to StringLiteral#camelcase must be a bool, not NumberLiteral"
      end

      it "executes underscore" do
        assert_macro %({{"FooBar".underscore}}), %("foo_bar")
      end

      it "executes titleize" do
        assert_macro %({{"hello world".titleize}}), %("Hello World")
      end

      it "executes to_utf16" do
        assert_macro %({{"hello".to_utf16}}), "(::Slice(::UInt16).literal(104_u16, 101_u16, 108_u16, 108_u16, 111_u16, 0_u16))[0, 5]"
        assert_macro %({{"TEST 😐🐙 ±∀ の".to_utf16}}), "(::Slice(::UInt16).literal(84_u16, 69_u16, 83_u16, 84_u16, 32_u16, 55357_u16, 56848_u16, 55357_u16, 56345_u16, 32_u16, 177_u16, 8704_u16, 32_u16, 12398_u16, 0_u16))[0, 14]"
      end

      it "executes to_i" do
        assert_macro %({{"1234".to_i}}), %(1234)
      end

      it "executes to_i(base)" do
        assert_macro %({{"1234".to_i(16)}}), %(4660)
      end

      it "executes string includes? char (true)" do
        assert_macro %({{"spice".includes?('s')}}), %(true)
        assert_macro %({{"spice".includes?('p')}}), %(true)
        assert_macro %({{"spice".includes?('i')}}), %(true)
        assert_macro %({{"spice".includes?('c')}}), %(true)
        assert_macro %({{"spice".includes?('e')}}), %(true)
      end

      it "executes string includes? char (false)" do
        assert_macro %({{"spice".includes?('S')}}), %(false)
        assert_macro %({{"spice".includes?(' ')}}), %(false)
        assert_macro %({{"spice".includes?('!')}}), %(false)
        assert_macro %({{"spice".includes?('b')}}), %(false)
      end

      it "executes string includes? string (true)" do
        assert_macro %({{"spice".includes?("s")}}), %(true)
        assert_macro %({{"spice".includes?("e")}}), %(true)
        assert_macro %({{"spice".includes?("sp")}}), %(true)
        assert_macro %({{"spice".includes?("ce")}}), %(true)
        assert_macro %({{"spice".includes?("pic")}}), %(true)
      end

      it "executes string includes? string (false)" do
        assert_macro %({{"spice".includes?("Spi")}}), %(false)
        assert_macro %({{"spice".includes?(" spi")}}), %(false)
        assert_macro %({{"spice".includes?("ce ")}}), %(false)
        assert_macro %({{"spice".includes?("b")}}), %(false)
        assert_macro %({{"spice".includes?("spice ")}}), %(false)
      end
    end

    describe "macro id methods" do
      it "forwards methods to string" do
        assert_macro %({{x.ends_with?("llo")}}), %(true), {x: MacroId.new("hello")}
        assert_macro %({{x.ends_with?("tro")}}), %(false), {x: MacroId.new("hello")}
        assert_macro %({{x.starts_with?("hel")}}), %(true), {x: MacroId.new("hello")}
        assert_macro %({{x.chomp}}), %(hello), {x: MacroId.new("hello\n")}
        assert_macro %({{x.upcase}}), %(HELLO), {x: MacroId.new("hello")}
        assert_macro %({{x.titleize}}), %(Hello World), {x: MacroId.new("hello world")}
        assert_macro %({{x.includes?("el")}}), %(true), {x: MacroId.new("hello")}
        assert_macro %({{x.includes?("he")}}), %(true), {x: MacroId.new("hello")}
        assert_macro %({{x.includes?("EL")}}), %(false), {x: MacroId.new("hello")}
        assert_macro %({{x.includes?("cat")}}), %(false), {x: MacroId.new("hello")}
      end

      it "compares with string" do
        assert_macro %({{x == "foo"}}), %(true), {x: MacroId.new("foo")}
        assert_macro %({{"foo" == x}}), %(true), {x: MacroId.new("foo")}

        assert_macro %({{x == "bar"}}), %(false), {x: MacroId.new("foo")}
        assert_macro %({{"bar" == x}}), %(false), {x: MacroId.new("foo")}

        assert_macro %({{x != "foo"}}), %(false), {x: MacroId.new("foo")}
        assert_macro %({{"foo" != x}}), %(false), {x: MacroId.new("foo")}

        assert_macro %({{x != "bar"}}), %(true), {x: MacroId.new("foo")}
        assert_macro %({{"bar" != x}}), %(true), {x: MacroId.new("foo")}
      end

      it "compares with symbol" do
        assert_macro %({{x == :foo}}), %(true), {x: MacroId.new("foo")}
        assert_macro %({{:foo == x}}), %(true), {x: MacroId.new("foo")}

        assert_macro %({{x == :bar}}), %(false), {x: MacroId.new("foo")}
        assert_macro %({{:bar == x}}), %(false), {x: MacroId.new("foo")}

        assert_macro %({{x != :foo}}), %(false), {x: MacroId.new("foo")}
        assert_macro %({{:foo != x}}), %(false), {x: MacroId.new("foo")}

        assert_macro %({{x != :bar}}), %(true), {x: MacroId.new("foo")}
        assert_macro %({{:bar != x}}), %(true), {x: MacroId.new("foo")}
      end
    end

    describe "symbol methods" do
      it "forwards methods to string" do
        assert_macro %({{x.ends_with?("llo")}}), %(true), {x: "hello".symbol}
        assert_macro %({{x.ends_with?("tro")}}), %(false), {x: "hello".symbol}
        assert_macro %({{x.starts_with?("hel")}}), %(true), {x: "hello".symbol}
        assert_macro %({{x.chomp}}), %(:hello), {x: SymbolLiteral.new("hello\n")}
        assert_macro %({{x.upcase}}), %(:HELLO), {x: "hello".symbol}
        assert_macro %({{x.titleize}}), %(:"Hello World"), {x: "hello world".symbol}
        assert_macro %({{x.includes?("el")}}), %(true), {x: "hello".symbol}
        assert_macro %({{x.includes?("he")}}), %(true), {x: "hello".symbol}
        assert_macro %({{x.includes?("EL")}}), %(false), {x: "hello".symbol}
        assert_macro %({{x.includes?("cat")}}), %(false), {x: "hello".symbol}
      end

      it "executes symbol == symbol" do
        assert_macro %({{:foo == :foo}}), %(true)
        assert_macro %({{:foo == :bar}}), %(false)
      end

      it "executes symbol != symbol" do
        assert_macro %({{:foo != :foo}}), %(false)
        assert_macro %({{:foo != :bar}}), %(true)
      end
    end

    describe "and methods" do
      it "executes left" do
        assert_macro %({{x.left}}), %(1), {x: And.new(1.int32, 2.int32)}
      end

      it "executes right" do
        assert_macro %({{x.right}}), %(2), {x: And.new(1.int32, 2.int32)}
      end
    end

    describe "or methods" do
      it "executes left" do
        assert_macro %({{x.left}}), %(1), {x: Or.new(1.int32, 2.int32)}
      end

      it "executes right" do
        assert_macro %({{x.right}}), %(2), {x: Or.new(1.int32, 2.int32)}
      end
    end

    describe ArrayLiteral do
      it "executes index 0" do
        assert_macro %({{[1, 2, 3][0]}}), "1"
      end

      it "executes index 1" do
        assert_macro %({{[1, 2, 3][1]}}), "2"
      end

      it "executes index out of bounds" do
        assert_macro %({{[1, 2, 3][3]}}), "nil"
      end

      it "executes size" do
        assert_macro %({{[1, 2, 3].size}}), "3"
      end

      it "executes empty?" do
        assert_macro %({{[1, 2, 3].empty?}}), "false"
      end

      it "executes identify" do
        assert_macro %({{"A::B".identify}}), "\"A__B\""
        assert_macro %({{"A".identify}}), "\"A\""
      end

      it "executes join" do
        assert_macro %({{[1, 2, 3].join ", "}}), %("1, 2, 3")
      end

      it "executes join with strings" do
        assert_macro %({{["a", "b"].join ", "}}), %("a, b")
      end

      it "executes map" do
        assert_macro %({{[1, 2, 3].map { |e| e == 2 }}}), "[false, true, false]"
      end

      it "executes *" do
        assert_macro %({{["na"] * 5}}), %(["na", "na", "na", "na", "na"])
      end

      it "executes reduce with no initial value" do
        assert_macro %({{[1, 2, 3].reduce { |acc, val| acc * val }}}), "6"
      end

      it "executes reduce with initial value" do
        assert_macro %({{[1, 2, 3].reduce(4) { |acc, val| acc * val }}}), "24"
        assert_macro %({{[1, 2, 3].reduce([] of NumberLiteral) { |acc, val| acc = [val]+acc }}}), "[3, 2, 1]"
      end

      it "executes map with constants" do
        assert_macro %({{x.map { |e| e.id }}}), "[Foo, Bar]", {x: ArrayLiteral.new([Path.new("Foo"), Path.new("Bar")] of ASTNode)}
      end

      it "executes map with arg" do
        assert_macro %({{x.map { |e| e.id }}}), "[hello]", {x: ArrayLiteral.new(["hello".call] of ASTNode)}
      end

      describe "#map_with_index" do
        context "with both arguments" do
          it "returns the resulting array" do
            assert_macro %({{[1, 2, 3].map_with_index { |e, idx| e == 2 || idx <= 1 }}}), %([true, true, false])
          end
        end

        context "without the index argument" do
          it "returns the resulting array" do
            assert_macro %({{[1, 2, 3].map_with_index { |e| e }}}), %([1, 2, 3])
          end
        end

        context "without the element argument" do
          it "returns the resulting array" do
            assert_macro %({{[1, 2, 3].map_with_index { |_, idx| idx }}}), %([0, 1, 2])
          end
        end

        context "without either argument" do
          it "returns the resulting array" do
            assert_macro %({{[1, 2, 3].map_with_index { 7 }}}), %([7, 7, 7])
          end
        end
      end

      it "#each" do
        assert_macro(
          %({% begin %}{% values = [] of Nil %}{% [1, 2, 3].each { |v| values << v } %}{{values}}{% end %}),
          %([1, 2, 3])
        )
      end

      describe "#each_with_index" do
        context "with both arguments" do
          it "builds the correct array" do
            assert_macro(
              %({% begin %}{% values = [] of Nil %}{% [1, 2, 3].each_with_index { |v, idx| values << (v + idx) } %}{{values}}{% end %}),
              %([1, 3, 5])
            )
          end
        end

        context "without the index argument" do
          it "builds the correct array" do
            assert_macro(
              %({% begin %}{% values = [] of Nil %}{% [1, 2, 3].each_with_index { |v| values << v } %}{{values}}{% end %}),
              %([1, 2, 3])
            )
          end
        end

        context "without the element argument" do
          it "builds the correct array" do
            assert_macro(
              %({% begin %}{% values = [] of Nil %}{% [1, 2, 3].each_with_index { |_, idx| values << idx } %}{{values}}{% end %}),
              %([0, 1, 2])
            )
          end
        end

        context "without either argument" do
          it "builds the correct array" do
            assert_macro(
              %({% begin %}{% values = [] of Nil %}{% [1, 2, 3].each_with_index { values << 7 } %}{{values}}{% end %}),
              %([7, 7, 7])
            )
          end
        end
      end

      it "executes select" do
        assert_macro %({{[1, 2, 3].select { |e| e == 1 }}}), "[1]"
      end

      it "executes reject" do
        assert_macro %({{[1, 2, 3].reject { |e| e == 1 }}}), "[2, 3]"
      end

      it "executes find (finds)" do
        assert_macro %({{[1, 2, 3].find { |e| e == 2 }}}), "2"
      end

      it "executes find (doesn't find)" do
        assert_macro %({{[1, 2, 3].find { |e| e == 4 }}}), "nil"
      end

      it "executes any? (true)" do
        assert_macro %({{[1, 2, 3].any? { |e| e == 1 }}}), "true"
      end

      it "executes any? (false)" do
        assert_macro %({{[1, 2, 3].any? { |e| e == 4 }}}), "false"
      end

      it "executes all? (true)" do
        assert_macro %({{[1, 1, 1].all? { |e| e == 1 }}}), "true"
      end

      it "executes all? (false)" do
        assert_macro %({{[1, 2, 1].all? { |e| e == 1 }}}), "false"
      end

      it "executes first" do
        assert_macro %({{[1, 2, 3].first}}), "1"
      end

      it "executes last" do
        assert_macro %({{[1, 2, 3].last}}), "3"
      end

      it "executes splat" do
        assert_macro %({{[1, 2, 3].splat}}), "1, 2, 3"
      end

      it "executes splat with symbols and strings" do
        assert_macro %({{[:foo, "hello", 3].splat}}), %(:foo, "hello", 3)
      end

      it "executes splat with splat" do
        assert_macro %({{*[1, 2, 3]}}), "1, 2, 3"
      end

      it "executes is_a?" do
        assert_macro %({{[1, 2, 3].is_a?(ArrayLiteral)}}), "true"
        assert_macro %({{[1, 2, 3].is_a?(ASTNode)}}), "true"
        assert_macro %({{[1, 2, 3].is_a?(NumberLiteral)}}), "false"
      end

      it "creates an array literal with a var" do
        assert_macro %({% a = [x] %}{{a[0]}}), "1", {x: 1.int32}
      end

      it "executes sort with numbers" do
        assert_macro %({{[3, 2, 1].sort}}), "[1, 2, 3]"
      end

      it "executes sort with strings" do
        assert_macro %({{["c", "b", "a"].sort}}), %(["a", "b", "c"])
      end

      it "executes sort with ids" do
        assert_macro %({{["c".id, "b".id, "a".id].sort}}), %([a, b, c])
      end

      it "executes sort with ids and strings" do
        assert_macro %({{["c".id, "b", "a".id].sort}}), %([a, "b", c])
      end

      it "executes sort_by" do
        assert_macro %({{["abc", "a", "ab"].sort_by { |x| x.size }}}), %(["a", "ab", "abc"])
      end

      it "calls block exactly once for each element in #sort_by" do
        assert_macro <<-CRYSTAL, %(5)
          {{ (i = 0; ["abc", "a", "ab", "abcde", "abcd"].sort_by { i += 1 }; i) }}
          CRYSTAL
      end

      it "executes uniq" do
        assert_macro %({{[1, 1, 1, 2, 3, 1, 2, 3, 4].uniq}}), %([1, 2, 3, 4])
      end

      it "executes unshift" do
        assert_macro %({% x = [1]; x.unshift(2); %}{{x}}), %([2, 1])
      end

      it "executes push" do
        assert_macro %({% x = [1]; x.push(2); x << 3 %}{{x}}), %([1, 2, 3])
      end

      it "executes includes?" do
        assert_macro %({{ [1, 2, 3].includes?(1) }}), %(true)
        assert_macro %({{ [1, 2, 3].includes?(4) }}), %(false)
      end

      describe "#+" do
        context "with TupleLiteral argument" do
          it "concatenates the literals into an ArrayLiteral" do
            assert_macro %({{ [1, 2] + {3, 4, 5} }}), %([1, 2, 3, 4, 5])
          end
        end

        context "with ArrayLiteral argument" do
          it "concatenates the literals into an ArrayLiteral" do
            assert_macro %({{ [1, 2] + [3, 4, 5] }}), %([1, 2, 3, 4, 5])
          end
        end
      end

      describe "#-" do
        context "with TupleLiteral argument" do
          it "removes the elements in RHS from LHS into an ArrayLiteral" do
            assert_macro %({{ [1, 2, 3, 4] - {1, 3, 5} }}), %([2, 4])
          end
        end

        context "with ArrayLiteral argument" do
          it "removes the elements in RHS from LHS into an ArrayLiteral" do
            assert_macro %({{ [1, 2, 3, 4] - [1, 3, 5] }}), %([2, 4])
          end
        end
      end

      it "executes [] with range" do
        assert_macro %({{ [1, 2, 3, 4][1...-1] }}), %([2, 3])
      end

      it "executes [] with computed range" do
        assert_macro %({{ [1, 2, 3, 4][[1].size...-1] }}), %([2, 3])
      end

      it "executes [] with incomplete range" do
        assert_macro %({{ [1, 2, 3, 4][1..] }}), %([2, 3, 4])
        assert_macro %({{ [1, 2, 3, 4][1..nil] }}), %([2, 3, 4])
        assert_macro %({{ [1, 2, 3, 4][...2] }}), %([1, 2])
        assert_macro %({{ [1, 2, 3, 4][nil...2] }}), %([1, 2])
        assert_macro %({{ [1, 2, 3, 4][..] }}), %([1, 2, 3, 4])
        assert_macro %({{ [1, 2, 3, 4][nil..nil] }}), %([1, 2, 3, 4])
      end

      it "executes [] with range, start is out of bounds" do
        assert_macro %({{ [1, 2, 3, 4][5..] }}), %(nil)
        assert_macro %({{ [1, 2, 3, 4][-5..] }}), %(nil)
      end

      it "executes [] with two numbers" do
        assert_macro %({{ [1, 2, 3, 4, 5][1, 3] }}), %([2, 3, 4])
      end

      it "executes [] with two numbers, start is out of bounds" do
        assert_macro %({{ [1, 2, 3, 4][5, 1] }}), %(nil)
        assert_macro %({{ [1, 2, 3, 4][-5, 4] }}), %(nil)
      end

      it "executes []=" do
        assert_macro %({% a = [0]; a[0] = 2 %}{{a[0]}}), "2"
      end

      it "executes of" do
        assert_macro %({{ x.of }}), %(Int64), {x: ArrayLiteral.new([] of ASTNode, of: Path.new("Int64"))}
      end

      it "executes of (nop)" do
        assert_macro %({{ [1, 2, 3].of }}), %()
      end

      it "executes type" do
        assert_macro %({{ x.type }}), %(Deque), {x: ArrayLiteral.new([] of ASTNode, name: Path.new("Deque"))}
      end

      it "executes type (nop)" do
        assert_macro %({{ [1, 2, 3].type }}), %()
      end
    end

    describe HashLiteral do
      it "executes size" do
        assert_macro %({{{:a => 1, :b => 3}.size}}), "2"
      end

      it "executes empty?" do
        assert_macro %({{{:a => 1}.empty?}}), "false"
      end

      it "executes []" do
        assert_macro %({{{:a => 1}[:a]}}), "1"
      end

      it "executes [] not found" do
        assert_macro %({{{:a => 1}[:b]}}), "nil"
      end

      it "executes keys" do
        assert_macro %({{{:a => 1, :b => 2}.keys}}), "[:a, :b]"
      end

      it "executes values" do
        assert_macro %({{{:a => 1, :b => 2}.values}}), "[1, 2]"
      end

      it "executes map" do
        assert_macro %({{{:a => 1, :b => 2}.map {|k, v| k == :a && v == 1}}}), "[true, false]"
      end

      it "executes is_a?" do
        assert_macro %({{{:a => 1}.is_a?(HashLiteral)}}), "true"
        assert_macro %({{{:a => 1}.is_a?(ASTNode)}}), "true"
        assert_macro %({{{:a => 1}.is_a?(RangeLiteral)}}), "false"
      end

      it "executes []=" do
        assert_macro %({% a = {} of Nil => Nil; a[1] = 2 %}{{a[1]}}), "2"
      end

      it "creates a hash literal with a var" do
        assert_macro %({% a = {:a => x} %}{{a[:a]}}), "1", {x: 1.int32}
      end

      it "executes to_a" do
        assert_macro %({{{:a => 1, :b => 3}.to_a}}), "[{:a, 1}, {:b, 3}]"
      end

      it "executes of_key" do
        of = HashLiteral::Entry.new(Path.new("String"), Path.new("UInt8"))
        assert_macro %({{ x.of_key }}), %(String), {x: HashLiteral.new([] of HashLiteral::Entry, of: of)}
      end

      it "executes of_key (nop)" do
        assert_macro %({{ {'z' => 6, 'a' => 9}.of_key }}), %()
      end

      it "executes of_value" do
        of = HashLiteral::Entry.new(Path.new("String"), Path.new("UInt8"))
        assert_macro %({{ x.of_value }}), %(UInt8), {x: HashLiteral.new([] of HashLiteral::Entry, of: of)}
      end

      it "executes of_value (nop)" do
        assert_macro %({{ {'z' => 6, 'a' => 9}.of_value }}), %()
      end

      it "executes has_key?" do
        assert_macro %({{ {'z' => 6, 'a' => 9}.has_key?('z') }}), %(true)
        assert_macro %({{ {'z' => 6, 'a' => 9}.has_key?('x') }}), %(false)
        assert_macro %({{ {'z' => nil, 'a' => 9}.has_key?('z') }}), %(true)
      end

      it "executes type" do
        assert_macro %({{ x.type }}), %(Headers), {x: HashLiteral.new([] of HashLiteral::Entry, name: Path.new("Headers"))}
      end

      it "executes type (nop)" do
        assert_macro %({{ {'z' => 6, 'a' => 9}.type }}), %()
      end

      it "executes double splat" do
        assert_macro %({{**{1 => 2, 3 => 4}}}), "1 => 2, 3 => 4"
      end

      it "executes double splat" do
        assert_macro %({{{1 => 2, 3 => 4}.double_splat}}), "1 => 2, 3 => 4"
      end

      it "executes double splat with arg" do
        assert_macro %({{{1 => 2, 3 => 4}.double_splat(", ")}}), "1 => 2, 3 => 4, "
      end

      describe "#each" do
        context "with both arguments" do
          it "builds the correct array" do
            assert_macro(
              %({% begin %}{% values = [] of Nil %}{% {"k1" => "v1", "k2" => "v2"}.each { |k, v| values << {k, v} } %}{{values}}{% end %}),
              %([{"k1", "v1"}, {"k2", "v2"}])
            )
          end
        end

        context "without the value argument" do
          it "builds the correct array" do
            assert_macro(
              %({% begin %}{% values = [] of Nil %}{% {"k1" => "v1", "k2" => "v2"}.each { |k| values << k } %}{{values}}{% end %}),
              %(["k1", "k2"])
            )
          end
        end

        context "without the key argument" do
          it "builds the correct array" do
            assert_macro(
              %({% begin %}{% values = [] of Nil %}{% {"k1" => "v1", "k2" => "v2"}.each { |_, v| values << v } %}{{values}}{% end %}),
              %(["v1", "v2"])
            )
          end
        end

        context "without either argument" do
          it "builds the correct array" do
            assert_macro(
              %({% begin %}{% values = [] of Nil %}{% {"k1" => "v1", "k2" => "v2"}.each { values << {"k3", "v3"} } %}{{values}}{% end %}),
              %([{"k3", "v3"}, {"k3", "v3"}])
            )
          end
        end
      end

      it "executes select with block" do
        assert_macro %({{{:a => 1, :b => 2, :c => 3}.select { |k, v| v > 1 }}}), "{:b => 2, :c => 3}"
      end

      it "executes select with keys" do
        assert_macro %({{{:a => 1, :b => 2, "c" => 3}.select(:a, "c")}}), "{:a => 1, \"c\" => 3}"
      end

      it "executes reject with block" do
        assert_macro %({{{:a => 1, :b => 2, :c => 3}.reject { |k, v| v > 1 }}}), "{:a => 1}"
      end

      it "executes reject with keys" do
        assert_macro %({{{:a => 1, :b => 2, :c => 3}.reject(:b)}}), "{:a => 1, :c => 3}"
      end
    end

    describe NamedTupleLiteral do
      it "executes size" do
        assert_macro %({{{a: 1, b: 3}.size}}), "2"
      end

      it "executes empty?" do
        assert_macro %({{{a: 1}.empty?}}), "false"
      end

      it "executes []" do
        assert_macro %({{{a: 1}[:a]}}), "1"
        assert_macro %({{{a: 1}["a"]}}), "1"
      end

      it "executes [] not found" do
        assert_macro %({{{a: 1}[:b]}}), "nil"
        assert_macro %({{{a: 1}["b"]}}), "nil"
      end

      it "executes [] with invalid key type" do
        assert_macro_error %({{{a: 1}[true]}}), "argument to [] must be a symbol or string, not BoolLiteral"
      end

      it "executes keys" do
        assert_macro %({{{a: 1, b: 2}.keys}}), "[a, b]"
      end

      it "executes values" do
        assert_macro %({{{a: 1, b: 2}.values}}), "[1, 2]"
      end

      it "executes map" do
        assert_macro %({{{a: 1, b: 2}.map {|k, v| k.stringify == "a" && v == 1}}}), "[true, false]"
      end

      it "executes is_a?" do
        assert_macro %({{{a: 1}.is_a?(NamedTupleLiteral)}}), "true"
        assert_macro %({{{a: 1}.is_a?(ASTNode)}}), "true"
        assert_macro %({{{a: 1}.is_a?(RangeLiteral)}}), "false"
      end

      it "executes []=" do
        assert_macro %({% a = {a: 1}; a[:a] = 2 %}{{a[:a]}}), "2"
        assert_macro %({% a = {a: 1}; a["a"] = 2 %}{{a["a"]}}), "2"
      end

      it "executes has_key?" do
        assert_macro %({{{a: 1}.has_key?("a")}}), "true"
        assert_macro %({{{a: 1}.has_key?(:a)}}), "true"
        assert_macro %({{{a: nil}.has_key?("a")}}), "true"
        assert_macro %({{{a: nil}.has_key?("b")}}), "false"
        assert_macro_error %({{{a: 1}.has_key?(true)}}), "expected 'NamedTupleLiteral#has_key?' first argument to be a SymbolLiteral, StringLiteral or MacroId, not BoolLiteral"
      end

      it "creates a named tuple literal with a var" do
        assert_macro %({% a = {a: x} %}{{a[:a]}}), "1", {x: 1.int32}
      end

      it "executes to_a" do
        assert_macro %({{{a: 1, b: 3}.to_a}}), "[{a, 1}, {b, 3}]"
      end

      it "executes double splat" do
        assert_macro %({{**{a: 1, "foo bar": 2, "+": 3}}}), %(a: 1, "foo bar": 2, "+": 3)
      end

      it "executes double splat" do
        assert_macro %({{{a: 1, "foo bar": 2, "+": 3}.double_splat}}), %(a: 1, "foo bar": 2, "+": 3)
      end

      it "executes double splat with arg" do
        assert_macro %({{{a: 1, "foo bar": 2, "+": 3}.double_splat(", ")}}), %(a: 1, "foo bar": 2, "+": 3, )
      end

      describe "#each" do
        context "with both arguments" do
          it "builds the correct array" do
            assert_macro(
              %({% begin %}{% values = [] of Nil %}{% {k1: "v1", k2: "v2"}.each { |k, v| values << {k, v} } %}{{values}}{% end %}),
              %([{k1, "v1"}, {k2, "v2"}])
            )
          end
        end

        context "without the value argument" do
          it "builds the correct array" do
            assert_macro(
              %({% begin %}{% values = [] of Nil %}{% {k1: "v1", k2: "v2"}.each { |k| values << k } %}{{values}}{% end %}),
              %([k1, k2])
            )
          end
        end

        context "without the key argument" do
          it "builds the correct array" do
            assert_macro(
              %({% begin %}{% values = [] of Nil %}{% {k1: "v1", k2: "v2"}.each { |_, v| values << v } %}{{values}}{% end %}),
              %(["v1", "v2"])
            )
          end
        end

        context "without either argument" do
          it "builds the correct array" do
            assert_macro(
              %({% begin %}{% values = [] of Nil %}{% {k1: "v1", k2: "v2"}.each { values << {"k3", "v3"} } %}{{values}}{% end %}),
              %([{"k3", "v3"}, {"k3", "v3"}])
            )
          end
        end
      end

      it "executes select with block" do
        assert_macro %({{ {a: 1, b: 2, c: 3}.select { |k, v| v > 1 } }}), "{b: 2, c: 3}"
      end

      it "executes select with keys" do
        assert_macro %({{ {a: 1, b: 2, c: 3}.select("a", "c") }}), "{a: 1, c: 3}"
      end

      it "executes reject with block" do
        assert_macro %({{ {a: 1, b: 2, c: 3}.reject { |k, v| v > 1 } }}), "{a: 1}"
      end

      it "executes reject with keys" do
        assert_macro %({{ {a: 1, b: 2, c: 3}.reject(:b) }}), "{a: 1, c: 3}"
      end
    end

    describe TupleLiteral do
      it "executes [] with 0" do
        assert_macro %({{ {1, 2, 3}[0] }}), "1"
      end

      it "executes [] with 1" do
        assert_macro %({{ {1, 2, 3}[1] }}), "2"
      end

      it "executes [] out of bounds" do
        assert_macro %({{ {1, 2, 3}[3] }}), "nil"
      end

      it "executes [] with range" do
        assert_macro %({{ {1, 2, 3, 4}[1...-1] }}), %({2, 3})
      end

      it "executes [] with computed range" do
        assert_macro %({{ {1, 2, 3, 4}[[1].size...-1] }}), %({2, 3})
      end

      it "executes [] with incomplete range" do
        assert_macro %({{ {1, 2, 3, 4}[1..] }}), %({2, 3, 4})
        assert_macro %({{ {1, 2, 3, 4}[1..nil] }}), %({2, 3, 4})
        assert_macro %({{ {1, 2, 3, 4}[...2] }}), %({1, 2})
        assert_macro %({{ {1, 2, 3, 4}[nil...2] }}), %({1, 2})
        assert_macro %({{ {1, 2, 3, 4}[..] }}), %({1, 2, 3, 4})
        assert_macro %({{ {1, 2, 3, 4}[nil..nil] }}), %({1, 2, 3, 4})
      end

      it "executes [] with range, start is out of bounds" do
        assert_macro %({{ {1, 2, 3, 4}[5..] }}), %(nil)
        assert_macro %({{ {1, 2, 3, 4}[-5..] }}), %(nil)
      end

      it "executes [] with two numbers" do
        assert_macro %({{ {1, 2, 3, 4, 5}[1, 3] }}), %({2, 3, 4})
      end

      it "executes [] with two numbers, start is out of bounds" do
        assert_macro %({{ {1, 2, 3, 4}[5, 1] }}), %(nil)
        assert_macro %({{ {1, 2, 3, 4}[-5, 4] }}), %(nil)
      end

      it "executes size" do
        assert_macro %({{ {1, 2, 3}.size }}), "3"
      end

      it "executes empty?" do
        assert_macro %({{ {1, 2, 3}.empty? }}), "false"
      end

      it "executes join" do
        assert_macro %({{ {1, 2, 3}.join ", " }}), %("1, 2, 3")
      end

      it "executes join with strings" do
        assert_macro %({{ {"a", "b"}.join ", " }}), %("a, b")
      end

      it "executes map" do
        assert_macro %({{ {1, 2, 3}.map { |e| e == 2 } }}), "{false, true, false}"
      end

      it "executes map with constants" do
        assert_macro %({{x.map { |e| e.id }}}), "{Foo, Bar}", {x: TupleLiteral.new([Path.new("Foo"), Path.new("Bar")] of ASTNode)}
      end

      it "executes map with arg" do
        assert_macro %({{x.map { |e| e.id }}}), "{hello}", {x: TupleLiteral.new(["hello".call] of ASTNode)}
      end

      describe "#map_with_index" do
        context "with both arguments" do
          it "returns the resulting tuple" do
            assert_macro %({{{1, 2, 3}.map_with_index { |e, idx| e == 2 || idx <= 1 }}}), %({true, true, false})
          end
        end

        context "without the index argument" do
          it "returns the resulting tuple" do
            assert_macro %({{{1, 2, 3}.map_with_index { |e| e }}}), %({1, 2, 3})
          end
        end

        context "without the element argument" do
          it "returns the resulting tuple" do
            assert_macro %({{{1, 2, 3}.map_with_index { |_, idx| idx }}}), %({0, 1, 2})
          end
        end

        context "without either argument" do
          it "returns the resulting tuple" do
            assert_macro %({{{1, 2, 3}.map_with_index { 7 }}}), %({7, 7, 7})
          end
        end
      end

      it "#each" do
        assert_macro(
          %({% begin %}{% values = [] of Nil %}{% {1, 2, 3}.each { |v| values << v } %}{{values}}{% end %}),
          %([1, 2, 3])
        )
      end

      describe "#each_with_index" do
        context "with both arguments" do
          it "builds the correct array" do
            assert_macro(
              %({% begin %}{% values = [] of Nil %}{% {1, 2, 3}.each_with_index { |v, idx| values << (v + idx) } %}{{values}}{% end %}),
              %([1, 3, 5])
            )
          end
        end

        context "without the index argument" do
          it "builds the correct array" do
            assert_macro(
              %({% begin %}{% values = [] of Nil %}{% {1, 2, 3}.each_with_index { |v| values << v } %}{{values}}{% end %}),
              %([1, 2, 3])
            )
          end
        end

        context "without the element argument" do
          it "builds the correct array" do
            assert_macro(
              %({% begin %}{% values = [] of Nil %}{% {1, 2, 3}.each_with_index { |_, idx| values << idx } %}{{values}}{% end %}),
              %([0, 1, 2])
            )
          end
        end

        context "without either argument" do
          it "builds the correct array" do
            assert_macro(
              %({% begin %}{% values = [] of Nil %}{% {1, 2, 3}.each_with_index { values << 7 } %}{{values}}{% end %}),
              %([7, 7, 7])
            )
          end
        end
      end

      it "executes select" do
        assert_macro %({{ {1, 2, 3}.select { |e| e == 1 } }}), "{1}"
      end

      it "executes reject" do
        assert_macro %({{ {1, 2, 3}.reject { |e| e == 1 } }}), "{2, 3}"
      end

      it "executes find (finds)" do
        assert_macro %({{ {1, 2, 3}.find { |e| e == 2 } }}), "2"
      end

      it "executes find (doesn't find)" do
        assert_macro %({{ {1, 2, 3}.find { |e| e == 4 } }}), "nil"
      end

      it "executes any? (true)" do
        assert_macro %({{ {1, 2, 3}.any? { |e| e == 1 } }}), "true"
      end

      it "executes any? (false)" do
        assert_macro %({{ {1, 2, 3}.any? { |e| e == 4 } }}), "false"
      end

      it "executes all? (true)" do
        assert_macro %({{ {1, 1, 1}.all? { |e| e == 1 } }}), "true"
      end

      it "executes all? (false)" do
        assert_macro %({{ {1, 2, 1}.all? { |e| e == 1 } }}), "false"
      end

      it "executes first" do
        assert_macro %({{ {1, 2, 3}.first }}), "1"
      end

      it "executes last" do
        assert_macro %({{ {1, 2, 3}.last }}), "3"
      end

      it "executes splat" do
        assert_macro %({{ {1, 2, 3}.splat }}), "1, 2, 3"
      end

      it "executes splat with arg" do
        assert_macro %({{ {1, 2, 3}.splat(", ") }}), "1, 2, 3, "
      end

      it "executes splat with symbols and strings" do
        assert_macro %({{ {:foo, "hello", 3}.splat }}), %(:foo, "hello", 3)
      end

      it "executes splat with splat" do
        assert_macro %({{ *{1, 2, 3} }}), "1, 2, 3"
      end

      it "executes is_a?" do
        assert_macro %({{ {1, 2, 3}.is_a?(TupleLiteral) }}), "true"
        assert_macro %({{ {1, 2, 3}.is_a?(ASTNode) }}), "true"
        assert_macro %({{ {1, 2, 3}.is_a?(ArrayLiteral) }}), "false"
      end

      it "creates a tuple literal with a var" do
        assert_macro %({% a = {x} %}{{a[0]}}), "1", {x: 1.int32}
      end

      it "executes sort with numbers" do
        assert_macro %({{ {3, 2, 1}.sort }}), "{1, 2, 3}"
      end

      it "executes sort with strings" do
        assert_macro %({{ {"c", "b", "a"}.sort }}), %({"a", "b", "c"})
      end

      it "executes sort with ids" do
        assert_macro %({{ {"c".id, "b".id, "a".id}.sort }}), %({a, b, c})
      end

      it "executes sort with ids and strings" do
        assert_macro %({{ {"c".id, "b", "a".id}.sort }}), %({a, "b", c})
      end

      it "executes uniq" do
        assert_macro %({{ {1, 1, 1, 2, 3, 1, 2, 3, 4}.uniq }}), %({1, 2, 3, 4})
      end

      it "executes unshift" do
        assert_macro %({% x = {1}; x.unshift(2); %}{{x}}), %({2, 1})
      end

      it "executes push" do
        assert_macro %({% x = {1}; x.push(2); x << 3 %}{{x}}), %({1, 2, 3})
      end

      it "executes includes?" do
        assert_macro %({{ {1, 2, 3}.includes?(1) }}), %(true)
        assert_macro %({{ {1, 2, 3}.includes?(4) }}), %(false)
      end

      describe "#+" do
        context "with TupleLiteral argument" do
          it "concatenates the literals into a TupleLiteral" do
            assert_macro %({{ {1, 2} + {3, 4, 5} }}), %({1, 2, 3, 4, 5})
          end
        end

        context "with ArrayLiteral argument" do
          it "concatenates the literals into a TupleLiteral" do
            assert_macro %({{ {1, 2} + [3, 4, 5] }}), %({1, 2, 3, 4, 5})
          end
        end
      end

      describe "#-" do
        context "with TupleLiteral argument" do
          it "removes the elements in RHS from LHS into a TupleLiteral" do
            assert_macro %({{ {1, 2, 3, 4} - {1, 3, 5} }}), %({2, 4})
          end
        end

        context "with ArrayLiteral argument" do
          it "removes the elements in RHS from LHS into a TupleLiteral" do
            assert_macro %({{ {1, 2, 3, 4} - [1, 3, 5] }}), %({2, 4})
          end
        end
      end

      it "executes *" do
        assert_macro %({{ {"na"} * 5}}), %({"na", "na", "na", "na", "na"})
      end
    end

    describe "regex methods" do
      it "executes source" do
        assert_macro %({{ /rëgéx/i.source }}), %("rëgéx")
      end

      it "executes options" do
        assert_macro %({{ //.options }}), %([] of ::Symbol)
        assert_macro %({{ /a/i.options }}), %([:i] of ::Symbol)
        assert_macro %({{ /re/mix.options }}), %([:i, :m, :x] of ::Symbol)
      end
    end

    describe "metavar methods" do
      it "executes nothing" do
        assert_macro %({{x}}), %(foo), {x: MetaMacroVar.new("foo", Program.new.int32)}
      end

      it "executes name" do
        assert_macro %({{x.name}}), %(foo), {x: MetaMacroVar.new("foo", Program.new.int32)}
      end

      it "executes id" do
        assert_macro %({{x.id}}), %(foo), {x: MetaMacroVar.new("foo", Program.new.int32)}
      end

      it "executes is_a?" do
        assert_macro %({{x.is_a?(MetaVar)}}), %(true), {x: MetaMacroVar.new("foo", Program.new.int32)}
      end
    end

    describe "block methods" do
      it "executes body" do
        assert_macro %({{x.body}}), "1", {x: Block.new(body: 1.int32)}
      end

      it "executes args" do
        assert_macro %({{x.args}}), "[x, y]", {x: Block.new(["x".var, "y".var])}
      end

      it "executes splat_index" do
        assert_macro %({{x.splat_index}}), "1", {x: Block.new(["x".var, "y".var], splat_index: 1)}
        assert_macro %({{x.splat_index}}), "nil", {x: Block.new(["x".var, "y".var])}
      end
    end

    describe "expressions methods" do
      it "executes expressions" do
        assert_macro %({{x.body.expressions[0]}}), "some_call", {x: Block.new(body: Expressions.new(["some_call".call, "some_other_call".call] of ASTNode))}
      end
    end

    it "executes assign" do
      assert_macro %({{a = 1}}{{a}}), "11"
    end

    it "executes assign without output" do
      assert_macro %({% a = 1 %}{{a}}), "1"
    end

    describe TypeNode do
      describe "#includers" do
        it "returns an array of types `self` is directly included in" do
          assert_type(<<-CRYSTAL) { tuple_of([int32, int32, int32]) }
            module Foo
            end

            module Baz
              module Tar
                include Baz
              end
            end

            abstract class Parent
            end

            module Enumt(T)
              include Baz
            end

            class Bar < Parent
              include Foo
              include Baz
            end

            struct Str
              include Enumt(String)
              include Baz
            end

            struct Gen(T)
              include Baz
            end

            abstract struct AStr
              include Baz
            end

            abstract class ACla
              include Baz
            end

            class SubT(T)
              include Baz
            end

            class ChildT(T) < SubT(T)
              include Enumt(T)
            end

            class Witness < ChildT(String)
            end

            {
              {% if Baz.includers.map(&.stringify).sort == %w(ACla AStr Bar Baz::Tar Enumt(T) Gen(T) Str SubT(T)) %} 1 {% else %} 'a' {% end %},
              {% if Enumt.includers.map(&.stringify).sort == %w(ChildT(String) ChildT(T) Str) %} 1 {% else %} 'a' {% end %},
              {% if Enumt(String).includers.map(&.stringify).sort == %w(ChildT(String) Str) %} 1 {% else %} 'a' {% end %},
            }
            CRYSTAL
        end
      end

      describe "#name" do
        describe "simple type" do
          it "returns the name of the type" do
            assert_macro("{{x.name}}", "String") do |program|
              {x: TypeNode.new(program.string)}
            end
          end
        end

        describe "namespaced type" do
          it "should return the FQN of the type" do
            assert_macro("{{type.name}}", "SomeModule::SomeType") do |program|
              mod = NonGenericModuleType.new(program, program, "SomeModule")

              klass = NonGenericClassType.new(program, mod, "SomeType", program.reference)

              {type: TypeNode.new(klass)}
            end
          end
        end

        describe "generic type" do
          it "includes the generic_args of the type by default" do
            assert_macro("{{klass.name}}", "SomeType(A, B)") do |program|
              {klass: TypeNode.new(GenericClassType.new(program, program, "SomeType", program.object, ["A", "B"]))}
            end
          end

          it "includes the generic_args of the instantiated type by default" do
            assert_macro("{{Array(Int32).name}}", "Array(Int32)")
          end
        end

        describe "generic instance" do
          it "prints generic type arguments" do
            assert_macro("{{klass.name}}", "Foo(Int32, 3)") do |program|
              generic_type = GenericClassType.new(program, program, "Foo", program.reference, ["T", "U"])
              {klass: TypeNode.new(generic_type.instantiate([program.int32, 3.int32] of TypeVar))}
            end
          end

          it "prints empty splat type var" do
            assert_macro("{{klass.name}}", "Foo()") do |program|
              generic_type = GenericClassType.new(program, program, "Foo", program.reference, ["T"])
              generic_type.splat_index = 0
              {klass: TypeNode.new(generic_type.instantiate([] of TypeVar))}
            end
          end

          it "prints multiple arguments for splat type var" do
            assert_macro("{{klass.name}}", "Foo(Int32, String)") do |program|
              generic_type = GenericClassType.new(program, program, "Foo", program.reference, ["T"])
              generic_type.splat_index = 0
              {klass: TypeNode.new(generic_type.instantiate([program.int32, program.string] of TypeVar))}
            end
          end

          it "does not print extra commas for empty splat type var (1)" do
            assert_macro("{{klass.name}}", "Foo(Int32)") do |program|
              generic_type = GenericClassType.new(program, program, "Foo", program.reference, ["T", "U"])
              generic_type.splat_index = 1
              {klass: TypeNode.new(generic_type.instantiate([program.int32] of TypeVar))}
            end
          end

          it "does not print extra commas for empty splat type var (2)" do
            assert_macro("{{klass.name}}", "Foo(Int32)") do |program|
              generic_type = GenericClassType.new(program, program, "Foo", program.reference, ["T", "U"])
              generic_type.splat_index = 0
              {klass: TypeNode.new(generic_type.instantiate([program.int32] of TypeVar))}
            end
          end

          it "does not print extra commas for empty splat type var (3)" do
            assert_macro("{{klass.name}}", "Foo(Int32, String)") do |program|
              generic_type = GenericClassType.new(program, program, "Foo", program.reference, ["T", "U", "V"])
              generic_type.splat_index = 1
              {klass: TypeNode.new(generic_type.instantiate([program.int32, program.string] of TypeVar))}
            end
          end
        end

        describe :generic_args do
          describe true do
            it "includes the generic_args of the type" do
              assert_macro("{{klass.name(generic_args: true)}}", "SomeType(A, B)") do |program|
                {klass: TypeNode.new(GenericClassType.new(program, program, "SomeType", program.object, ["A", "B"]))}
              end
            end

            it "includes the generic_args of the instantiated type" do
              assert_macro("{{Array(Int32).name(generic_args: true)}}", "Array(Int32)")
            end
          end

          describe false do
            it "does not include the generic_args of the type" do
              assert_macro("{{klass.name(generic_args: false)}}", "SomeType") do |program|
                {klass: TypeNode.new(GenericClassType.new(program, program, "SomeType", program.object, ["A", "B"]))}
              end
            end

            it "does not include the generic_args of the instantiated type" do
              assert_macro("{{Array(Int32).name(generic_args: false)}}", "Array")
            end
          end

          describe "with an invalid type argument" do
            it "should raise the proper exception" do
              assert_macro_error("{{x.name(generic_args: 99)}}", "named argument 'generic_args' to TypeNode#name must be a BoolLiteral, not NumberLiteral") do |program|
                {x: TypeNode.new(program.string)}
              end
            end
          end
        end
      end

      describe "#id" do
        it "does not include trailing + for virtual type" do
          assert_macro("{{klass.id}}", "Foo") do |program|
            foo = NonGenericClassType.new(program, program, "Foo", program.reference)
            NonGenericClassType.new(program, program, "Bar", foo)
            {klass: TypeNode.new(foo.virtual_type)}
          end
        end
      end

      describe "#warning" do
        it "emits a warning at a specific node" do
          assert_warning <<-CRYSTAL, "Oh noes"
            macro test(node)
              {% node.warning "Oh noes" %}
            end

            test 10
          CRYSTAL
        end
      end

      describe "#instance_vars" do
        it "executes instance_vars" do
          assert_macro("{{x.instance_vars.map &.stringify}}", %(["bytesize", "length", "c"])) do |program|
            {x: TypeNode.new(program.string)}
          end
        end

        it "errors when called from top-level scope" do
          assert_error <<-CRYSTAL, "`TypeNode#instance_vars` cannot be called in the top-level scope: instance vars are not yet initialized"
            class Foo
            end
            {{ Foo.instance_vars }}
          CRYSTAL
        end

        it "does not error when called from def scope" do
          assert_type <<-CRYSTAL { |program| program.string }
            module Moo
            end
            def moo
              {{ Moo.instance_vars.stringify }}
            end
            moo
          CRYSTAL
        end
      end

      it "executes class vars" do
        assert_macro("{{x.class_vars.map &.name}}", %([class_var])) do |program|
          klass = NonGenericClassType.new(program, program, "SomeType", program.reference)
          declare_class_var(klass, "@@class_var", program.string)
          {x: TypeNode.new(klass)}
        end
      end

      it "executes class vars (with inheritance)" do
        assert_macro("{{x.class_vars.map &.name}}", %([child_class_var, base_class_var, mod_class_var])) do |program|
          base_class = NonGenericClassType.new(program, program, "BaseType", program.reference)
          declare_class_var(base_class, "@@base_class_var", program.string)
          mod = NonGenericModuleType.new(program, program, "SomeModule")
          declare_class_var(mod, "@@mod_class_var", program.string)
          base_class.include mod
          child_class = NonGenericClassType.new(program, program, "ChildType", base_class)
          declare_class_var(child_class, "@@child_class_var", program.string)
          {x: TypeNode.new(child_class)}
        end
      end

      it "executes instance_vars on metaclass" do
        assert_macro("{{x.class.instance_vars.map &.stringify}}", %([])) do |program|
          klass = NonGenericClassType.new(program, program, "SomeType", program.reference)
          klass.declare_instance_var("@var", program.string)
          {x: TypeNode.new(klass)}
        end
      end

      it "executes class_vars on metaclass" do
        assert_macro("{{x.class.class_vars.map &.stringify}}", %([])) do |program|
          klass = NonGenericClassType.new(program, program, "SomeType", program.reference)
          declare_class_var(klass, "@@class_var", program.string)
          {x: TypeNode.new(klass)}
        end
      end

      it "executes instance_vars on symbol type" do
        assert_macro("{{x.instance_vars.map &.stringify}}", %([])) do |program|
          {x: TypeNode.new(program.symbol)}
        end
      end

      it "executes class_vars on symbol type" do
        assert_macro("{{x.class_vars.map &.stringify}}", %([])) do |program|
          {x: TypeNode.new(program.symbol)}
        end
      end

      describe "#methods" do
        it "only includes methods defined on the type itself, excluding descendants and ancestors" do
          assert_macro("{{x.methods.map &.name}}", %([parent_method])) do |program|
            mixin = NonGenericModuleType.new(program, program, "Mixin")
            mixin.add_def Def.new "mixin_method"

            ancestor_type = NonGenericClassType.new(program, program, "AncestorType", program.reference)
            ancestor_type.add_def Def.new "parent_method"
            ancestor_type.include mixin

            some_type = NonGenericClassType.new(program, program, "SomeType", ancestor_type)
            some_type.add_def Def.new "foo"

            {x: TypeNode.new(ancestor_type)}
          end
        end
      end

      describe "#all_methods" do
        it "includes methods defined on a type and its ancestors" do
          assert_macro("{{x.all_methods.map &.name}}", %([child_method, parent_method, mixin_method])) do |program|
            mixin = NonGenericModuleType.new(program, program, "Mixin")
            mixin.add_def Def.new "mixin_method"

            ancestor_type = NonGenericClassType.new(program, program, "AncestorType", program.reference)
            ancestor_type.add_def Def.new "parent_method"
            ancestor_type.include mixin

            some_type = NonGenericClassType.new(program, program, "SomeType", ancestor_type)
            some_type.add_def Def.new "child_method"

            {x: TypeNode.new(some_type)}
          end
        end

        it "only includes methods defined on the type itself and ancestors, but excludes descendants" do
          assert_macro("{{x.all_methods.map &.name}}", %([parent_method, mixin_method])) do |program|
            mixin = NonGenericModuleType.new(program, program, "Mixin")
            mixin.add_def Def.new "mixin_method"

            ancestor_type = NonGenericClassType.new(program, program, "AncestorType", program.reference)
            ancestor_type.add_def Def.new "parent_method"
            ancestor_type.include mixin

            some_type = NonGenericClassType.new(program, program, "SomeType", ancestor_type)
            some_type.add_def Def.new "foo"

            {x: TypeNode.new(ancestor_type)}
          end
        end
      end

      it "executes class methods" do
        assert_macro("{{x.class.methods.map &.name}}", %([allocate])) do |program|
          klass = NonGenericClassType.new(program, program, "SomeType", program.reference)
          {x: TypeNode.new(klass)}
        end
      end

      it "executes ancestors" do
        assert_macro("{{x.ancestors}}", %([SomeModule, Reference, Object])) do |program|
          mod = NonGenericModuleType.new(program, program, "SomeModule")
          klass = NonGenericClassType.new(program, program, "SomeType", program.reference)
          klass.include mod

          {x: TypeNode.new(klass)}
        end
      end

      it "executes ancestors (with generic)" do
        assert_macro("{{x.ancestors}}", %([SomeGenericModule(String), SomeGenericType(String), Reference, Object])) do |program|
          generic_type = GenericClassType.new(program, program, "SomeGenericType", program.reference, ["T"])
          generic_mod = GenericModuleType.new(program, program, "SomeGenericModule", ["T"])
          type_var = {"T" => TypeNode.new(program.string)} of String => ASTNode
          type = GenericClassInstanceType.new(program, generic_type, program.reference, type_var)
          mod = GenericModuleInstanceType.new(program, generic_mod, type_var)

          klass = NonGenericClassType.new(program, program, "SomeType", type)
          klass.include mod

          {x: TypeNode.new(klass)}
        end
      end

      it "executes superclass" do
        assert_macro("{{x.superclass}}", %(Reference)) do |program|
          {x: TypeNode.new(program.string)}
        end
      end

      it "executes size of tuple" do
        assert_macro("{{x.size}}", "2") do |program|
          {x: TypeNode.new(program.tuple_of([program.int32, program.string] of TypeVar))}
        end
      end

      it "executes size of tuple metaclass" do
        assert_macro("{{x.size}}", "2") do |program|
          {x: TypeNode.new(program.tuple_of([program.int32, program.string] of TypeVar).metaclass)}
        end
      end

      it "executes type_vars" do
        assert_macro("{{x.type_vars.map &.stringify}}", %(["A", "B"])) do |program|
          {x: TypeNode.new(GenericClassType.new(program, program, "SomeType", program.object, ["A", "B"]))}
        end
        assert_macro("{{x.type_vars.map &.stringify}}", %(["Int32", "String"])) do |program|
          generic_class = GenericClassType.new(program, program, "SomeType", program.object, ["A", "B"])
          {x: TypeNode.new(generic_class.instantiate([program.int32, program.string] of TypeVar))}
        end
        assert_macro("{{x.type_vars.map &.stringify}}", %(["Tuple(Int32, String)"])) do |program|
          generic_class = GenericClassType.new(program, program, "SomeType", program.object, ["T"])
          generic_class.splat_index = 0
          {x: TypeNode.new(generic_class.instantiate([program.int32, program.string] of TypeVar))}
        end
        assert_macro("{{x.type_vars.map &.stringify}}", %(["Tuple()"])) do |program|
          generic_class = GenericClassType.new(program, program, "SomeType", program.object, ["T"])
          generic_class.splat_index = 0
          {x: TypeNode.new(generic_class.instantiate([] of TypeVar))}
        end
      end

      it "executes class" do
        assert_macro("{{x.class.name}}", "String.class") do |program|
          {x: TypeNode.new(program.string)}
        end
      end

      it "executes instance" do
        assert_macro("{{x.class.instance}}", "String") do |program|
          {x: TypeNode.new(program.string)}
        end
      end

      it "executes ==" do
        assert_macro("{{x == Reference}}", "false") do |program|
          {x: TypeNode.new(program.string)}
        end
        assert_macro("{{x == String}}", "true") do |program|
          {x: TypeNode.new(program.string)}
        end
      end

      it "executes !=" do
        assert_macro("{{x != Reference}}", "true") do |program|
          {x: TypeNode.new(program.string)}
        end
        assert_macro("{{x != String}}", "false") do |program|
          {x: TypeNode.new(program.string)}
        end
      end

      it "== and != devirtualize generic type arguments (#10730)" do
        assert_type(<<-CRYSTAL) { tuple_of([int32, char]) }
          class A
          end

          class B < A
          end

          module Foo(T)
            def self.foo
              {
                {% if T == A %} 1 {% else %} 'a' {% end %},
                {% if T != A %} 1 {% else %} 'a' {% end %},
              }
            end
          end

          Foo(A).foo
          CRYSTAL
      end

      it "executes <" do
        assert_macro("{{x < Reference}}", "true") do |program|
          {x: TypeNode.new(program.string)}
        end
        assert_macro("{{x < String}}", "false") do |program|
          {x: TypeNode.new(program.string)}
        end
      end

      it "executes <=" do
        assert_macro("{{x <= Reference}}", "true") do |program|
          {x: TypeNode.new(program.string)}
        end
        assert_macro("{{x <= String}}", "true") do |program|
          {x: TypeNode.new(program.string)}
        end
      end

      it "executes >" do
        assert_macro("{{x > Reference}}", "false") do |program|
          {x: TypeNode.new(program.reference)}
        end
        assert_macro("{{x > String}}", "true") do |program|
          {x: TypeNode.new(program.reference)}
        end
      end

      it "executes >=" do
        assert_macro("{{x >= Reference}}", "true") do |program|
          {x: TypeNode.new(program.reference)}
        end
        assert_macro("{{x >= String}}", "true") do |program|
          {x: TypeNode.new(program.reference)}
        end
      end

      describe "#abstract?" do
        it NonGenericModuleType do
          assert_macro("{{type.abstract?}}", "false") do |program|
            mod = NonGenericModuleType.new(program, program, "SomeModule")

            {type: TypeNode.new(mod)}
          end
        end

        it GenericModuleType do
          assert_macro("{{type.abstract?}}", "false") do |program|
            generic_mod = GenericModuleType.new(program, program, "SomeGenericModule", ["T"])

            {type: TypeNode.new(generic_mod)}
          end
        end

        describe NonGenericClassType do
          describe "class" do
            it "abstract" do
              assert_macro("{{type.abstract?}}", "true") do |program|
                klass = NonGenericClassType.new(program, program, "SomeType", program.reference)
                klass.abstract = true

                {type: TypeNode.new(klass)}
              end
            end

            it "non-abstract" do
              assert_macro("{{type.abstract?}}", "false") do |program|
                klass = NonGenericClassType.new(program, program, "SomeType", program.reference)

                {type: TypeNode.new(klass)}
              end
            end
          end

          describe "struct" do
            it "abstract" do
              assert_macro("{{type.abstract?}}", "true") do |program|
                klass = NonGenericClassType.new(program, program, "SomeType", program.reference)
                klass.abstract = true
                klass.struct = true

                {type: TypeNode.new(klass)}
              end
            end

            it "non-abstract" do
              assert_macro("{{type.abstract?}}", "false") do |program|
                klass = NonGenericClassType.new(program, program, "SomeType", program.reference)
                klass.struct = true

                {type: TypeNode.new(klass)}
              end
            end
          end
        end

        describe GenericClassType do
          describe "class" do
            it "abstract" do
              assert_macro("{{type.abstract?}}", "true") do |program|
                klass = GenericClassType.new(program, program, "SomeGenericType", program.reference, ["T"])
                klass.abstract = true

                {type: TypeNode.new(klass)}
              end
            end

            it "non-abstract" do
              assert_macro("{{type.abstract?}}", "false") do |program|
                klass = GenericClassType.new(program, program, "SomeGenericType", program.reference, ["T"])

                {type: TypeNode.new(klass)}
              end
            end
          end

          describe "struct" do
            it "abstract" do
              assert_macro("{{type.abstract?}}", "true") do |program|
                klass = GenericClassType.new(program, program, "SomeGenericType", program.reference, ["T"])
                klass.abstract = true
                klass.struct = true

                {type: TypeNode.new(klass)}
              end
            end

            it "non-abstract" do
              assert_macro("{{type.abstract?}}", "false") do |program|
                klass = GenericClassType.new(program, program, "SomeGenericType", program.reference, ["T"])
                klass.struct = true

                {type: TypeNode.new(klass)}
              end
            end
          end
        end
      end

      describe "#union?" do
        it true do
          assert_macro("{{x.union?}}", "true") do |program|
            {x: TypeNode.new(program.union_of(program.string, program.nil))}
          end
        end

        it false do
          assert_macro("{{x.union?}}", "false") do |program|
            {x: TypeNode.new(program.string)}
          end
        end
      end

      describe "#module?" do
        it NonGenericModuleType do
          assert_macro("{{type.module?}}", "true") do |program|
            mod = NonGenericModuleType.new(program, program, "SomeModule")

            {type: TypeNode.new(mod)}
          end
        end

        it GenericModuleType do
          assert_macro("{{type.module?}}", "true") do |program|
            generic_mod = GenericModuleType.new(program, program, "SomeGenericModule", ["T"])

            {type: TypeNode.new(generic_mod)}
          end
        end

        describe NonGenericClassType do
          it "class" do
            assert_macro("{{type.module?}}", "false") do |program|
              klass = NonGenericClassType.new(program, program, "SomeType", program.reference)

              {type: TypeNode.new(klass)}
            end
          end

          it "struct" do
            assert_macro("{{type.module?}}", "false") do |program|
              klass = NonGenericClassType.new(program, program, "SomeType", program.reference)
              klass.struct = true

              {type: TypeNode.new(klass)}
            end
          end
        end

        describe GenericClassType do
          it "class" do
            assert_macro("{{type.module?}}", "false") do |program|
              klass = GenericClassType.new(program, program, "SomeGenericType", program.reference, ["T"])

              {type: TypeNode.new(klass)}
            end
          end

          it "struct" do
            assert_macro("{{type.module?}}", "false") do |program|
              klass = GenericClassType.new(program, program, "SomeGenericType", program.reference, ["T"])
              klass.struct = true

              {type: TypeNode.new(klass)}
            end
          end
        end
      end

      describe "#class?" do
        it NonGenericModuleType do
          assert_macro("{{type.class?}}", "false") do |program|
            mod = NonGenericModuleType.new(program, program, "SomeModule")

            {type: TypeNode.new(mod)}
          end
        end

        it GenericModuleType do
          assert_macro("{{type.class?}}", "false") do |program|
            generic_mod = GenericModuleType.new(program, program, "SomeGenericModule", ["T"])

            {type: TypeNode.new(generic_mod)}
          end
        end

        describe NonGenericClassType do
          it "class" do
            assert_macro("{{type.class?}}", "true") do |program|
              klass = NonGenericClassType.new(program, program, "SomeType", program.reference)

              {type: TypeNode.new(klass)}
            end
          end

          it "struct" do
            assert_macro("{{type.class?}}", "false") do |program|
              klass = NonGenericClassType.new(program, program, "SomeType", program.reference)
              klass.struct = true

              {type: TypeNode.new(klass)}
            end
          end
        end

        describe GenericClassType do
          it "class" do
            assert_macro("{{type.class?}}", "true") do |program|
              klass = GenericClassType.new(program, program, "SomeGenericType", program.reference, ["T"])

              {type: TypeNode.new(klass)}
            end
          end

          it "struct" do
            assert_macro("{{type.class?}}", "false") do |program|
              klass = GenericClassType.new(program, program, "SomeGenericType", program.reference, ["T"])
              klass.struct = true

              {type: TypeNode.new(klass)}
            end
          end
        end
      end

      describe "#struct?" do
        it NonGenericModuleType do
          assert_macro("{{type.struct?}}", "false") do |program|
            mod = NonGenericModuleType.new(program, program, "SomeModule")

            {type: TypeNode.new(mod)}
          end
        end

        it GenericModuleType do
          assert_macro("{{type.struct?}}", "false") do |program|
            generic_mod = GenericModuleType.new(program, program, "SomeGenericModule", ["T"])

            {type: TypeNode.new(generic_mod)}
          end
        end

        describe NonGenericClassType do
          it "class" do
            assert_macro("{{type.struct?}}", "false") do |program|
              klass = NonGenericClassType.new(program, program, "SomeType", program.reference)

              {type: TypeNode.new(klass)}
            end
          end

          it "struct" do
            assert_macro("{{type.struct?}}", "true") do |program|
              klass = NonGenericClassType.new(program, program, "SomeType", program.reference)
              klass.struct = true

              {type: TypeNode.new(klass)}
            end
          end
        end

        describe GenericClassType do
          it "class" do
            assert_macro("{{type.struct?}}", "false") do |program|
              klass = GenericClassType.new(program, program, "SomeGenericType", program.reference, ["T"])

              {type: TypeNode.new(klass)}
            end
          end

          it "struct" do
            assert_macro("{{type.struct?}}", "true") do |program|
              klass = GenericClassType.new(program, program, "SomeGenericType", program.reference, ["T"])
              klass.struct = true

              {type: TypeNode.new(klass)}
            end
          end
        end
      end

      describe "#nilable?" do
        it false do
          assert_macro("{{x.nilable?}}", "false") do |program|
            {x: TypeNode.new(program.string)}
          end

          assert_macro("{{x.nilable?}}", "false") do |program|
            {x: TypeNode.new(program.union_of(program.string, program.int32))}
          end

          assert_macro("{{x.nilable?}}", "false") do |program|
            {x: TypeNode.new(program.no_return)}
          end

          assert_macro("{{x.nilable?}}", "false") do |program|
            {x: TypeNode.new(program.class_type)}
          end

          assert_macro("{{x.nilable?}}", "false") do |program|
            {x: TypeNode.new(program.reference)}
          end
        end

        it true do
          assert_macro("{{x.nilable?}}", "true") do |program|
            {x: TypeNode.new(program.nil_type)}
          end

          assert_macro("{{x.nilable?}}", "true") do |program|
            {x: TypeNode.new(program.union_of(program.string, program.nil))}
          end

          assert_macro("{{x.nilable?}}", "true") do |program|
            {x: TypeNode.new(program.value)}
          end

          assert_macro("{{x.nilable?}}", "true") do |program|
            {x: TypeNode.new(program.object)}
          end

          assert_macro("{{x.nilable?}}", "true") do |program|
            mod = NonGenericModuleType.new(program, program, "SomeModule")
            program.nil_type.include mod
            {x: TypeNode.new(mod)}
          end

          assert_type(<<-CRYSTAL) { int32 }
            class Foo(T)
            end

            alias Bar = Foo(Bar)?

            {{ Bar.nilable? ? 1 : 'a' }}
            CRYSTAL
        end
      end

      it "executes resolve" do
        assert_macro("{{x.resolve}}", "String") do |program|
          {x: TypeNode.new(program.string)}
        end
      end

      it "executes resolve?" do
        assert_macro("{{x.resolve?}}", "String") do |program|
          {x: TypeNode.new(program.string)}
        end
      end

      it "executes union_types (union)" do
        assert_macro("{{x.union_types}}", %([Bool, Int32])) do |program|
          {x: TypeNode.new(program.union_of(program.int32, program.bool))}
        end
      end

      it "executes union_types (non-union)" do
        assert_macro("{{x.union_types}}", %([Int32])) do |program|
          {x: TypeNode.new(program.int32)}
        end
      end

      describe "executes private?" do
        it false do
          assert_macro("{{x.private?}}", "false") do |program|
            klass = GenericClassType.new(program, program, "SomeGenericType", program.reference, ["T"])

            {x: TypeNode.new(klass)}
          end
        end

        it true do
          assert_macro("{{x.private?}}", "true") do |program|
            klass = GenericClassType.new(program, program, "SomeGenericType", program.reference, ["T"])
            klass.private = true

            {x: TypeNode.new(klass)}
          end
        end
      end

      describe "public?" do
        it false do
          assert_macro("{{x.public?}}", "false") do |program|
            klass = GenericClassType.new(program, program, "SomeGenericType", program.reference, ["T"])
            klass.private = true

            {x: TypeNode.new(klass)}
          end
        end

        it true do
          assert_macro("{{x.public?}}", "true") do |program|
            klass = GenericClassType.new(program, program, "SomeGenericType", program.reference, ["T"])

            {x: TypeNode.new(klass)}
          end
        end
      end

      describe "visibility" do
        it :public do
          assert_macro("{{x.visibility}}", ":public") do |program|
            klass = GenericClassType.new(program, program, "SomeGenericType", program.reference, ["T"])

            {x: TypeNode.new(klass)}
          end
        end

        it :private do
          assert_macro("{{x.visibility}}", ":private") do |program|
            klass = GenericClassType.new(program, program, "SomeGenericType", program.reference, ["T"])
            klass.private = true

            {x: TypeNode.new(klass)}
          end
        end
      end

      describe "#has_inner_pointers?" do
        it "works on structs" do
          assert_macro("{{x.has_inner_pointers?}}", %(false)) do |program|
            klass = NonGenericClassType.new(program, program, "SomeType", program.struct)
            klass.struct = true
            klass.declare_instance_var("@var", program.int32)
            {x: TypeNode.new(klass)}
          end

          assert_macro("{{x.has_inner_pointers?}}", %(true)) do |program|
            klass = NonGenericClassType.new(program, program, "SomeType", program.struct)
            klass.struct = true
            klass.declare_instance_var("@var", program.string)
            {x: TypeNode.new(klass)}
          end
        end

        it "works on references" do
          assert_macro("{{x.has_inner_pointers?}}", %(true)) do |program|
            klass = NonGenericClassType.new(program, program, "SomeType", program.reference)
            {x: TypeNode.new(klass)}
          end
        end

        it "works on ReferenceStorage" do
          assert_macro("{{x.has_inner_pointers?}}", %(false)) do |program|
            reference_storage = GenericReferenceStorageType.new program, program, "ReferenceStorage", program.struct, ["T"]
            klass = NonGenericClassType.new(program, program, "SomeType", program.reference)
            klass.declare_instance_var("@var", program.int32)
            {x: TypeNode.new(reference_storage.instantiate([klass] of TypeVar))}
          end

          assert_macro("{{x.has_inner_pointers?}}", %(true)) do |program|
            reference_storage = GenericReferenceStorageType.new program, program, "ReferenceStorage", program.struct, ["T"]
            klass = NonGenericClassType.new(program, program, "SomeType", program.reference)
            klass.declare_instance_var("@var", program.string)
            {x: TypeNode.new(reference_storage.instantiate([klass] of TypeVar))}
          end
        end

        it "works on primitive values" do
          assert_macro("{{x.has_inner_pointers?}}", %(false)) do |program|
            {x: TypeNode.new(program.int32)}
          end

          assert_macro("{{x.has_inner_pointers?}}", %(true)) do |program|
            {x: TypeNode.new(program.void)}
          end

          assert_macro("{{x.has_inner_pointers?}}", %(true)) do |program|
            {x: TypeNode.new(program.pointer_of(program.int32))}
          end

          assert_macro("{{x.has_inner_pointers?}}", %(true)) do |program|
            {x: TypeNode.new(program.proc_of(program.void))}
          end
        end

        it "errors when called from top-level scope" do
          assert_error <<-CRYSTAL, "`TypeNode#has_inner_pointers?` cannot be called in the top-level scope: instance vars are not yet initialized"
            class Foo
            end
            {{ Foo.has_inner_pointers? }}
          CRYSTAL
        end

        it "does not error when called from def scope" do
          assert_type <<-CRYSTAL { |program| program.bool }
            module Moo
            end
            def moo
              {{ Moo.has_inner_pointers? }}
            end
            moo
          CRYSTAL
        end
      end
    end

    describe "type declaration methods" do
      it "executes var" do
        assert_macro %({{x.var}}), "some_name", {x: TypeDeclaration.new(Var.new("some_name"), Path.new("SomeType"))}
      end

      it "executes var when instance var" do
        assert_macro %({{x.var}}), "@some_name", {x: TypeDeclaration.new(InstanceVar.new("@some_name"), Path.new("SomeType"))}
      end

      it "executes type" do
        assert_macro %({{x.type}}), "SomeType", {x: TypeDeclaration.new(Var.new("some_name"), Path.new("SomeType"))}
      end

      it "executes value" do
        assert_macro %({{x.value}}), "1", {x: TypeDeclaration.new(Var.new("some_name"), Path.new("SomeType"), 1.int32)}
      end
    end

    describe "uninitialized var methods" do
      it "executes var" do
        assert_macro %({{x.var}}), "some_name", {x: UninitializedVar.new(Var.new("some_name"), Path.new("SomeType"))}
      end

      it "executes type" do
        assert_macro %({{x.type}}), "SomeType", {x: UninitializedVar.new(Var.new("some_name"), Path.new("SomeType"))}
      end
    end

    describe "proc notation methods" do
      it "gets single input" do
        assert_macro %({{x.inputs}}), "[SomeType]", {x: ProcNotation.new(([Path.new("SomeType")] of ASTNode), Path.new("SomeResult"))}
      end

      it "gets single output" do
        assert_macro %({{x.output}}), "SomeResult", {x: ProcNotation.new(([Path.new("SomeType")] of ASTNode), Path.new("SomeResult"))}
      end

      it "gets multiple inputs" do
        assert_macro %({{x.inputs}}), "[SomeType, OtherType]", {x: ProcNotation.new([Path.new("SomeType"), Path.new("OtherType")] of ASTNode)}
      end

      it "gets empty output" do
        assert_macro %({{x.output}}), "nil", {x: ProcNotation.new([Path.new("SomeType")] of ASTNode)}
      end

      it "executes resolve" do
        assert_macro %({{x.resolve}}), "Proc(Int32, String)", {x: ProcNotation.new(([Path.new("Int32")] of ASTNode), Path.new("String"))}

        assert_macro_error(%({{x.resolve}}), "undefined constant Foo") do
          {x: ProcNotation.new(([Path.new("Foo")] of ASTNode))}
        end

        assert_macro_error(%({{x.resolve}}), "undefined constant Foo") do
          {x: ProcNotation.new(([] of ASTNode), Path.new("Foo"))}
        end
      end

      it "executes resolve?" do
        assert_macro %({{x.resolve?}}), "Proc(Int32, String)", {x: ProcNotation.new(([Path.new("Int32")] of ASTNode), Path.new("String"))}
        assert_macro %({{x.resolve?}}), "nil", {x: ProcNotation.new(([Path.new("Foo")] of ASTNode))}
        assert_macro %({{x.resolve?}}), "nil", {x: ProcNotation.new(([] of ASTNode), Path.new("Foo"))}
      end
    end

    describe "proc literal methods" do
      it "executes body" do
        assert_macro %({{x.body}}), "1", {x: ProcLiteral.new(Def.new("->", body: 1.int32))}
      end

      it "executes args" do
        assert_macro %({{x.args}}), "[z]", {x: ProcLiteral.new(Def.new("->", [Arg.new("z")]))}
      end

      it "executes return_type" do
        assert_macro %({{x.return_type}}), "Int32", {x: ProcLiteral.new(Def.new("->", return_type: "Int32".path))}
        assert_macro %({{x.return_type}}), "", {x: ProcLiteral.new(Def.new("->"))}
      end
    end

    describe "proc pointer methods" do
      it "executes obj when present" do
        assert_macro %({{x.obj}}), "some_object", {x: ProcPointer.new(Var.new("some_object"), "method", [] of ASTNode)}
      end

      it "executes obj when absent" do
        assert_macro %({{x.obj}}), "nil", {x: ProcPointer.new(NilLiteral.new, "method", [] of ASTNode)}
      end

      it "executes name" do
        assert_macro %({{x.name}}), "method", {x: ProcPointer.new(Var.new("some_object"), "method", [] of ASTNode)}
      end

      it "executes args when empty" do
        assert_macro %({{x.args}}), "[]", {x: ProcPointer.new(Var.new("some_object"), "method", [] of ASTNode)}
      end

      it "executes args when not empty" do
        assert_macro %({{x.args}}), "[SomeType, OtherType]", {x: ProcPointer.new(Var.new("some_object"), "method", [Path.new("SomeType"), Path.new("OtherType")] of ASTNode)}
      end

      it "executes global?" do
        assert_macro %({{x.global?}}), "false", {x: ProcPointer.new(nil, "method")}
        assert_macro %({{x.global?}}), "true", {x: ProcPointer.new(nil, "method", global: true)}
        assert_macro %({{x.global?}}), "false", {x: ProcPointer.new(Path.global("Foo"), "method")}
      end
    end

    describe "def methods" do
      it "executes name" do
        assert_macro %({{x.name}}), "some_def", {x: Def.new("some_def")}
      end

      it "executes body" do
        assert_macro %({{x.body}}), "1", {x: Def.new("some_def", body: 1.int32)}
      end

      it "executes args" do
        assert_macro %({{x.args}}), "[z]", {x: Def.new("some_def", [Arg.new("z")])}
      end

      it "executes splat_index" do
        assert_macro %({{x.splat_index}}), "1", {x: Def.new("some_def", ["x".arg, "y".arg], splat_index: 1)}
        assert_macro %({{x.splat_index}}), "nil", {x: Def.new("some_def")}
      end

      it "executes double_splat" do
        assert_macro %({{x.double_splat}}), "s", {x: Def.new("some_def", ["x".arg, "y".arg], double_splat: "s".arg)}
        assert_macro %({{x.double_splat}}), "", {x: Def.new("some_def")}
      end

      it "executes block_arg" do
        assert_macro %({{x.block_arg}}), "b", {x: Def.new("some_def", ["x".arg, "y".arg], block_arg: "b".arg)}
        assert_macro %({{x.block_arg}}), "", {x: Def.new("some_def")}
      end

      it "executes accepts_block?" do
        assert_macro %({{x.accepts_block?}}), "true", {x: Def.new("some_def", ["x".arg, "y".arg], block_arity: 1)}
        assert_macro %({{x.accepts_block?}}), "false", {x: Def.new("some_def")}
      end

      it "executes return_type" do
        assert_macro %({{x.return_type}}), "b", {x: Def.new("some_def", ["x".arg, "y".arg], return_type: "b".arg)}
        assert_macro %({{x.return_type}}), "", {x: Def.new("some_def")}
      end

      it "executes free_vars" do
        assert_macro %({{x.free_vars}}), "[] of ::NoReturn", {x: Def.new("some_def")}
        assert_macro %({{x.free_vars}}), "[T]", {x: Def.new("some_def", free_vars: %w(T))}
        assert_macro %({{x.free_vars}}), "[T, U, V]", {x: Def.new("some_def", free_vars: %w(T U V))}
      end

      it "executes receiver" do
        assert_macro %({{x.receiver}}), "self", {x: Def.new("some_def", receiver: Var.new("self"))}
      end

      it "executes abstract?" do
        assert_macro %({{x.abstract?}}), "false", {x: Def.new("some_def")}
        assert_macro %({{x.abstract?}}), "true", {x: Def.new("some_def", abstract: true)}
      end

      it "executes visibility" do
        assert_macro %({{x.visibility}}), ":public", {x: Def.new("some_def")}
        assert_macro %({{x.visibility}}), ":private", {x: Def.new("some_def").tap { |d| d.visibility = Visibility::Private }}
      end
    end

    describe External do
      it "executes is_a?" do
        assert_macro %({{x.is_a?(External)}}), "true", {x: External.new("foo", [] of Arg, Nop.new, "foo")}
        assert_macro %({{x.is_a?(Def)}}), "true", {x: External.new("foo", [] of Arg, Nop.new, "foo")}
        assert_macro %({{x.is_a?(ASTNode)}}), "true", {x: External.new("foo", [] of Arg, Nop.new, "foo")}
      end
    end

    describe Primitive do
      it "executes name" do
        assert_macro %({{x.name}}), %(:abc), {x: Primitive.new("abc")}
        assert_macro %({{x.name}}), %(:"x.y.z"), {x: Primitive.new("x.y.z")}
      end
    end

    describe "macro methods" do
      it "executes name" do
        assert_macro %({{x.name}}), "some_macro", {x: Macro.new("some_macro")}
      end

      it "executes body" do
        assert_macro %({{x.body}}), "1", {x: Macro.new("some_macro", body: 1.int32)}
      end

      it "executes args" do
        assert_macro %({{x.args}}), "[z]", {x: Macro.new("some_macro", [Arg.new("z")])}
      end

      it "executes splat_index" do
        assert_macro %({{x.splat_index}}), "1", {x: Macro.new("some_macro", ["x".arg, "y".arg], splat_index: 1)}
        assert_macro %({{x.splat_index}}), "nil", {x: Macro.new("some_macro")}
      end

      it "executes double_splat" do
        assert_macro %({{x.double_splat}}), "s", {x: Macro.new("some_macro", ["x".arg, "y".arg], double_splat: "s".arg)}
        assert_macro %({{x.double_splat}}), "", {x: Macro.new("some_macro")}
      end

      it "executes block_arg" do
        assert_macro %({{x.block_arg}}), "b", {x: Macro.new("some_macro", ["x".arg, "y".arg], block_arg: "b".arg)}
        assert_macro %({{x.block_arg}}), "", {x: Macro.new("some_macro")}
      end

      it "executes visibility" do
        assert_macro %({{x.visibility}}), ":public", {x: Macro.new("some_macro")}
        assert_macro %({{x.visibility}}), ":private", {x: Macro.new("some_macro").tap { |d| d.visibility = Visibility::Private }}
      end
    end

    describe MacroExpression do
      it "executes exp" do
        assert_macro %({{x.exp}}), "nil", {x: MacroExpression.new(NilLiteral.new)}
      end

      it "executes output?" do
        assert_macro %({{x.output?}}), "false", {x: MacroExpression.new(NilLiteral.new, output: false)}
        assert_macro %({{x.output?}}), "true", {x: MacroExpression.new(1.int32, output: true)}
      end
    end

    describe "macro if methods" do
      it "executes cond" do
        assert_macro %({{x.cond}}), "true", {x: MacroIf.new(BoolLiteral.new(true), NilLiteral.new)}
      end

      it "executes then" do
        assert_macro %({{x.then}}), "\"test\"", {x: MacroIf.new(BoolLiteral.new(true), StringLiteral.new("test"), StringLiteral.new("foo"))}
      end

      it "executes else" do
        assert_macro %({{x.else}}), "\"foo\"", {x: MacroIf.new(BoolLiteral.new(true), StringLiteral.new("test"), StringLiteral.new("foo"))}
      end

      it "executes is_unless?" do
        assert_macro %({{x.is_unless?}}), "true", {x: MacroIf.new(BoolLiteral.new(true), StringLiteral.new("test"), StringLiteral.new("foo"), is_unless: true)}
        assert_macro %({{x.is_unless?}}), "false", {x: MacroIf.new(BoolLiteral.new(false), StringLiteral.new("test"), StringLiteral.new("foo"), is_unless: false)}
      end
    end

    describe "macro for methods" do
      it "executes vars" do
        assert_macro %({{x.vars}}), "[bar]", {x: MacroFor.new([Var.new("bar")], Var.new("foo"), Call.new("puts", [Var.new("bar")] of ASTNode))}
      end

      it "executes exp" do
        assert_macro %({{x.exp}}), "foo", {x: MacroFor.new([Var.new("bar")], Var.new("foo"), Call.new("puts", [Var.new("bar")] of ASTNode))}
      end

      it "executes body" do
        assert_macro %({{x.body}}), "puts(bar)", {x: MacroFor.new([Var.new("bar")], Var.new("foo"), Call.new("puts", [Var.new("bar")] of ASTNode))}
      end
    end

    describe MacroLiteral do
      it "executes value" do
        assert_macro %({{x.value}}), "foo(1)", {x: MacroLiteral.new("foo(1)")}
        assert_macro %({{x.value}}), "", {x: MacroLiteral.new("")}
      end
    end

    describe MacroVar do
      it "executes name" do
        assert_macro %({{x.name}}), "foo", {x: MacroVar.new("foo")}
      end

      it "executes expressions" do
        assert_macro %({{x.expressions}}), "[] of ::NoReturn", {x: MacroVar.new("foo")}
        assert_macro %({{x.expressions}}), "[x, 1]", {x: MacroVar.new("bar", [Var.new("x"), 1.int32] of ASTNode)}
      end
    end

    describe "unary expression methods" do
      it "executes exp" do
        assert_macro %({{x.exp}}), "some_call", {x: Not.new("some_call".call)}
      end

      it "executes is_a?" do
        assert_macro %({{ x.is_a?(Not) }}), "true", {x: Not.new("some_call".call)}
        assert_macro %({{ x.is_a?(Splat) }}), "false", {x: Not.new("some_call".call)}
        assert_macro %({{ x.is_a?(UnaryExpression) }}), "true", {x: Not.new("some_call".call)}
        assert_macro %({{ x.is_a?(ASTNode) }}), "true", {x: Not.new("some_call".call)}
        assert_macro %({{ x.is_a?(TypeNode) }}), "false", {x: Not.new("some_call".call)}
      end
    end

    describe "offsetof methods" do
      it "executes type" do
        assert_macro %({{x.type}}), "SomeType", {x: OffsetOf.new("SomeType".path, "@some_ivar".instance_var)}
      end

      it "executes offset" do
        assert_macro %({{x.offset}}), "@some_ivar", {x: OffsetOf.new("SomeType".path, "@some_ivar".instance_var)}
      end
    end

    describe Include do
      foo = Include.new("Foo".path)
      bar = Include.new(Generic.new("Bar".path, ["Int32".path] of ASTNode))

      it "executes name" do
        assert_macro %({{x.name}}), "Foo", {x: foo}
        assert_macro %({{x.name}}), "Bar(Int32)", {x: bar}
      end
    end

    describe Extend do
      foo = Extend.new("Foo".path)
      bar = Extend.new(Generic.new("Bar".path, ["Int32".path] of ASTNode))

      it "executes name" do
        assert_macro %({{x.name}}), "Foo", {x: foo}
        assert_macro %({{x.name}}), "Bar(Int32)", {x: bar}
      end
    end

    describe Alias do
      node = Alias.new("Foo".path, Generic.new(Path.new(["Bar", "Baz"], global: true), ["T".path] of ASTNode))

      it "executes name" do
        assert_macro %({{x.name}}), %(Foo), {x: node}
      end

      it "executes type" do
        assert_macro %({{x.type}}), %(::Bar::Baz(T)), {x: node}
      end
    end

    describe "visibility modifier methods" do
      node = VisibilityModifier.new(Visibility::Protected, Def.new("some_def"))

      it "executes visibility" do
        assert_macro %({{x.visibility}}), ":protected", {x: node}
      end

      it "executes exp" do
        assert_macro %({{x.exp}}), "def some_def\nend", {x: node}
      end
    end

    describe "is_a methods" do
      node = IsA.new("var".var, Path.new("Int32"))

      it "executes receiver" do
        assert_macro %({{x.receiver}}), "var", {x: node}
      end

      it "executes arg" do
        assert_macro %({{x.arg}}), "Int32", {x: node}
      end
    end

    describe "responds_to methods" do
      node = RespondsTo.new("var".var, "to_i")

      it "executes receiver" do
        assert_macro %({{x.receiver}}), "var", {x: node}
      end

      it "executes name" do
        assert_macro %({{x.name}}), %("to_i"), {x: node}
      end
    end

    describe "metaclass methods" do
      node = Metaclass.new(Path.new("Int32"))

      it "executes instance" do
        assert_macro %({{x.instance}}), "Int32", {x: node}
      end

      it "executes resolve" do
        assert_macro %({{x.resolve}}), %(Int32.class), {x: node}
        assert_macro %({{x.resolve}}), %(Array(T).class), {x: Metaclass.new(Path.new("Array"))}

        assert_macro_error(%({{x.resolve}}), "undefined constant Foo") do
          {x: Metaclass.new(Path.new("Foo"))}
        end
      end

      it "executes resolve?" do
        assert_macro %({{x.resolve?}}), %(Int32.class), {x: node}
        assert_macro %({{x.resolve?}}), %(Array(T).class), {x: Metaclass.new(Path.new("Array"))}
        assert_macro %({{x.resolve?}}), %(nil), {x: Metaclass.new(Path.new("Foo"))}
      end
    end

    describe "require methods" do
      it "executes path" do
        assert_macro %({{x.path}}), %("json"), {x: Require.new("json")}
      end
    end

    describe "call methods" do
      it "executes name" do
        assert_macro %({{x.name}}), "some_call", {x: "some_call".call}
      end

      it "executes args" do
        assert_macro %({{x.args}}), "[1, 3]", {x: Call.new("some_call", [1.int32, 3.int32] of ASTNode)}
      end

      it "executes receiver" do
        assert_macro %({{x.receiver}}), "1", {x: Call.new(1.int32, "some_call")}
      end

      it "executes block" do
        assert_macro %({{x.block}}), "do\nend", {x: Call.new(1.int32, "some_call", block: Block.new)}
      end

      it "executes block arg" do
        assert_macro %({{x.block_arg}}), "bl", {x: Call.new(1.int32, "some_call", block_arg: "bl".arg)}
      end

      it "executes block arg (nop)" do
        assert_macro %({{x.block_arg}}), "", {x: Call.new(1.int32, "some_call")}
      end

      it "executes named args" do
        assert_macro %({{x.named_args}}), "[a: 1, b: 2]", {x: Call.new(1.int32, "some_call", named_args: [NamedArgument.new("a", 1.int32), NamedArgument.new("b", 2.int32)])}
      end

      it "executes named args name" do
        assert_macro %({{x.named_args[0].name}}), "a", {x: Call.new(1.int32, "some_call", named_args: [NamedArgument.new("a", 1.int32), NamedArgument.new("b", 2.int32)])}
      end

      it "executes named args value" do
        assert_macro %({{x.named_args[0].value}}), "1", {x: Call.new(1.int32, "some_call", named_args: [NamedArgument.new("a", 1.int32), NamedArgument.new("b", 2.int32)])}
      end

      it "executes global?" do
        assert_macro %({{x.global?}}), "false", {x: Call.new(1.int32, "some_call")}
        assert_macro %({{x.global?}}), "true", {x: Call.new("some_call", global: true)}
      end
    end

    describe "arg methods" do
      it "executes name" do
        arg = "into".arg
        assert_macro %({{x.name}}), "into", {x: arg}
        arg.name = "array" # internal
        assert_macro %({{x.name}}), "into", {x: arg}
      end

      it "executes internal_name" do
        arg = "into".arg
        assert_macro %({{x.internal_name}}), "into", {x: arg}
        arg.name = "array"
        assert_macro %({{x.internal_name}}), "array", {x: arg}
      end

      it "executes default_value" do
        assert_macro %({{x.default_value}}), "1", {x: "some_arg".arg(default_value: 1.int32)}
      end

      it "executes restriction" do
        assert_macro %({{x.restriction}}), "T", {x: "some_arg".arg(restriction: "T".path)}
      end
    end

    describe "cast methods" do
      it "executes obj" do
        assert_macro %({{x.obj}}), "x", {x: Cast.new("x".call, "Int32".path)}
      end

      it "executes to" do
        assert_macro %({{x.to}}), "Int32", {x: Cast.new("x".call, "Int32".path)}
      end
    end

    describe "nilable cast methods" do
      it "executes obj" do
        assert_macro %({{x.obj}}), "x", {x: NilableCast.new("x".call, "Int32".path)}
      end

      it "executes to" do
        assert_macro %({{x.to}}), "Int32", {x: NilableCast.new("x".call, "Int32".path)}
      end
    end

    describe TypeOf do
      it "executes args" do
        assert_macro %({{x.args}}), "[1, 'a', Foo]", {x: TypeOf.new([1.int32, CharLiteral.new('a'), "Foo".path])}
      end
    end

    describe "case methods" do
      describe "when" do
        case_node = Case.new(1.int32, [When.new([2.int32, 3.int32] of ASTNode, 4.int32)], 5.int32, exhaustive: false)

        it "executes cond" do
          assert_macro %({{x.cond}}), "1", {x: case_node}
        end

        it "executes whens" do
          assert_macro %({{x.whens}}), "[when 2, 3\n  4\n]", {x: case_node}
        end

        it "executes when conds" do
          assert_macro %({{x.whens[0].conds}}), "[2, 3]", {x: case_node}
        end

        it "executes when body" do
          assert_macro %({{x.whens[0].body}}), "4", {x: case_node}
        end

        it "executes when exhaustive?" do
          assert_macro %({{x.whens[0].exhaustive?}}), "false", {x: case_node}
        end

        it "executes else" do
          assert_macro %({{x.else}}), "5", {x: case_node}
        end

        it "executes exhaustive?" do
          assert_macro %({{x.exhaustive?}}), "false", {x: case_node}
        end
      end

      describe "in" do
        case_node = Case.new(1.int32, [When.new([2.int32, 3.int32] of ASTNode, 4.int32)], 5.int32, exhaustive: true)

        it "executes whens" do
          assert_macro %({{x.whens}}), "[in 2, 3\n  4\n]", {x: case_node}
        end

        it "executes when exhaustive?" do
          assert_macro %({{x.whens[0].exhaustive?}}), "true", {x: case_node}
        end

        it "executes exhaustive?" do
          assert_macro %({{x.exhaustive?}}), "true", {x: case_node}
        end
      end
    end

    describe Select do
      it "executes whens" do
        assert_macro %({{x.whens}}), "[when foo\n  1\n]", {x: Select.new([When.new("foo".call, 1.int32)])}
        assert_macro %({{x.whens}}), "[when x = y\n  1\n, when bar\n]", {x: Select.new([When.new(Assign.new("x".var, "y".var), 1.int32), When.new("bar".call)])}
      end

      it "executes else" do
        assert_macro %({{x.else}}), "", {x: Select.new([When.new("foo".call)])}
        assert_macro %({{x.else}}), "1", {x: Select.new([When.new("foo".call)], 1.int32)}
        assert_macro %({{x.else}}), "nil", {x: Select.new([When.new("foo".call)], NilLiteral.new)}
      end
    end

    describe "if methods" do
      if_node = If.new(1.int32, 2.int32, 3.int32)

      it "executes cond" do
        assert_macro %({{x.cond}}), "1", {x: if_node}
      end

      it "executes then" do
        assert_macro %({{x.then}}), "2", {x: if_node}
      end

      it "executes else" do
        assert_macro %({{x.else}}), "3", {x: if_node}
      end

      it "executes else (nop)" do
        assert_macro %({{x.else}}), "", {x: If.new(1.int32, 2.int32)}
      end
    end

    describe "while methods" do
      while_node = While.new(1.int32, 2.int32)

      it "executes cond" do
        assert_macro %({{x.cond}}), "1", {x: while_node}
      end

      it "executes body" do
        assert_macro %({{x.body}}), "2", {x: while_node}
      end
    end

    describe "control expression methods" do
      it "executes exp" do
        assert_macro %({{x.exp}}), "1", {x: Break.new(1.int32)}
        assert_macro %({{x.exp}}), "1", {x: Next.new(1.int32)}
        assert_macro %({{x.exp}}), "1", {x: Return.new(1.int32)}
      end

      it "executes exp (nop)" do
        assert_macro %({{x.exp}}), "", {x: Break.new}
        assert_macro %({{x.exp}}), "", {x: Next.new}
        assert_macro %({{x.exp}}), "", {x: Return.new}
      end
    end

    describe "yield methods" do
      it "executes expressions" do
        assert_macro %({{x.expressions}}), "[]", {x: Yield.new}
        assert_macro %({{x.expressions}}), "[1]", {x: Yield.new([1.int32] of ASTNode)}
        assert_macro %({{x.expressions}}), "[1, 2]", {x: Yield.new([1.int32, 2.int32] of ASTNode)}
      end

      it "executes scope" do
        assert_macro %({{x.scope}}), "1", {x: Yield.new(scope: 1.int32)}
        assert_macro %({{x.scope}}), "nil", {x: Yield.new(scope: NilLiteral.new)}
      end

      it "executes scope (nop)" do
        assert_macro %({{x.scope}}), "", {x: Yield.new}
      end
    end

    describe "exception handler methods" do
      # begin
      #   1
      # rescue ex : Int32
      #   2
      # rescue Char | String
      # else
      #   3
      # ensure
      #   4
      # end
      begin_node = ExceptionHandler.new(1.int32, [Rescue.new(2.int32, ["Int32".path] of ASTNode, "ex"), Rescue.new(Nop.new, ["Char".path, "String".path] of ASTNode)], 3.int32, 4.int32)

      it "executes body" do
        assert_macro %({{x.body}}), "1", {x: begin_node}
      end

      it "executes rescues" do
        assert_macro %({{x.rescues}}), "[rescue ex : Int32\n  2\n, rescue Char | String\n]", {x: begin_node}
      end

      it "executes rescue body" do
        assert_macro %({{x.rescues[0].body}}), "2", {x: begin_node}
        assert_macro %({{x.rescues[1].body}}), "", {x: begin_node}
      end

      it "executes rescue types" do
        assert_macro %({{x.rescues[0].types}}), "[Int32]", {x: begin_node}
        assert_macro %({{x.rescues[1].types}}), "[Char, String]", {x: begin_node}
        assert_macro %({{x.types}}), "nil", {x: Rescue.new(1.int32)}
      end

      it "executes rescue name" do
        assert_macro %({{x.rescues[0].name}}), "ex", {x: begin_node}
        assert_macro %({{x.rescues[1].name}}), "", {x: begin_node}
      end

      it "executes else" do
        assert_macro %({{x.else}}), "3", {x: begin_node}
      end

      it "executes else (nop)" do
        assert_macro %({{x.else}}), "", {x: ExceptionHandler.new(Nop.new)}
      end

      it "executes ensure" do
        assert_macro %({{x.ensure}}), "4", {x: begin_node}
      end

      it "executes ensure (nop)" do
        assert_macro %({{x.ensure}}), "", {x: ExceptionHandler.new(Nop.new)}
      end
    end

    describe "assign methods" do
      it "executes target" do
        assert_macro %({{x.target}}), "foo", {x: Assign.new("foo".var, 2.int32)}
      end

      it "executes value" do
        assert_macro %({{x.value}}), "2", {x: Assign.new("foo".var, 2.int32)}
      end
    end

    describe "multi_assign methods" do
      multi_assign_node = MultiAssign.new(["foo".var, "bar".var] of ASTNode, [2.int32, "a".string] of ASTNode)

      it "executes targets" do
        assert_macro %({{x.targets}}), %([foo, bar]), {x: multi_assign_node}
      end

      it "executes values" do
        assert_macro %({{x.values}}), %([2, "a"]), {x: multi_assign_node}
      end
    end

    describe "instancevar methods" do
      it "executes name" do
        assert_macro %({{x.name}}), %(ivar), {x: InstanceVar.new("ivar")}
      end
    end

    describe "instancevar methods" do
      it "executes name" do
        assert_macro %({{x.name}}), %(ivar), {x: InstanceVar.new("ivar")}
      end
    end

    describe "readinstancevar methods" do
      it "executes obj" do
        assert_macro %({{x.obj}}), %(obj), {x: ReadInstanceVar.new("obj".var, "ivar")}
      end

      it "executes name" do
        assert_macro %({{x.name}}), %(ivar), {x: ReadInstanceVar.new("obj".var, "ivar")}
      end
    end

    describe "classvar methods" do
      it "executes name" do
        assert_macro %({{x.name}}), %(cvar), {x: ClassVar.new("cvar")}
      end
    end

    describe "global methods" do
      it "executes name" do
        assert_macro %({{x.name}}), %(gvar), {x: Global.new("gvar")}
      end
    end

    describe "splat methods" do
      it "executes exp" do
        assert_macro %({{x.exp}}), "2", {x: 2.int32.splat}
      end
    end

    describe "generic methods" do
      it "executes name" do
        assert_macro %({{x.name}}), "Foo", {x: Generic.new("Foo".path, ["T".path] of ASTNode)}
      end

      it "executes type_vars" do
        assert_macro %({{x.type_vars}}), "[T, U]", {x: Generic.new("Foo".path, ["T".path, "U".path] of ASTNode)}
        assert_macro %({{x.type_vars}}), "[]", {x: Generic.new("Foo".path, [] of ASTNode)}
      end

      it "executes named_args" do
        assert_macro %({{x.named_args}}), "{x: U, y: V}", {x: Generic.new("Foo".path, [] of ASTNode, named_args: [NamedArgument.new("x", "U".path), NamedArgument.new("y", "V".path)])}
      end

      it "executes resolve" do
        assert_macro %({{x.resolve}}), %(Array(String)), {x: Generic.new("Array".path, ["String".path] of ASTNode)}

        assert_macro_error %({{x.resolve}}), "undefined constant Foo", {x: Generic.new("Foo".path, ["String".path] of ASTNode)}
        assert_macro_error %({{x.resolve}}), "undefined constant Foo", {x: Generic.new("Array".path, ["Foo".path] of ASTNode)}
      end

      it "executes resolve?" do
        assert_macro %({{x.resolve?}}), %(Array(String)), {x: Generic.new("Array".path, ["String".path] of ASTNode)}
        assert_macro %({{x.resolve?}}), %(nil), {x: Generic.new("Foo".path, ["String".path] of ASTNode)}
        assert_macro %({{x.resolve?}}), %(nil), {x: Generic.new("Array".path, ["Foo".path] of ASTNode)}
      end

      it "executes types" do
        assert_macro %({{x.types}}), "[Foo(T)]", {x: Generic.new("Foo".path, ["T".path] of ASTNode)}
      end
    end

    describe "union methods" do
      it "executes types" do
        assert_macro %({{x.types}}), "[Int32, String]", {x: Crystal::Union.new(["Int32".path, "String".path] of ASTNode)}
      end

      it "executes resolve" do
        assert_macro %({{x.resolve}}), "(Int32 | String)", {x: Crystal::Union.new(["Int32".path, "String".path] of ASTNode)}
      end

      it "executes resolve?" do
        assert_macro %({{x.resolve?}}), "(Int32 | String)", {x: Crystal::Union.new(["Int32".path, "String".path] of ASTNode)}
        assert_macro %({{x.resolve?}}), "nil", {x: Crystal::Union.new(["Int32".path, "Unknown".path] of ASTNode)}
      end
    end

    describe RangeLiteral do
      it "executes begin" do
        assert_macro %({{x.begin}}), "1", {x: RangeLiteral.new(1.int32, 2.int32, true)}
      end

      it "executes end" do
        assert_macro %({{x.end}}), "2", {x: RangeLiteral.new(1.int32, 2.int32, true)}
      end

      it "executes excludes_end?" do
        assert_macro %({{x.excludes_end?}}), "true", {x: RangeLiteral.new(1.int32, 2.int32, true)}
      end

      it "executes map" do
        assert_macro %({{x.map(&.stringify)}}), %(["1", "2", "3"]), {x: RangeLiteral.new(1.int32, 3.int32, false)}
        assert_macro %({{x.map(&.stringify)}}), %(["1", "2"]), {x: RangeLiteral.new(1.int32, 3.int32, true)}
      end

      it "executes to_a" do
        assert_macro %({{x.to_a}}), %([1, 2, 3]), {x: RangeLiteral.new(1.int32, 3.int32, false)}
        assert_macro %({{x.to_a}}), %([1, 2]), {x: RangeLiteral.new(1.int32, 3.int32, true)}
      end

      it "#each" do
        assert_macro(
          %({% begin %}{% values = [] of Nil %}{% (1..3).each { |v| values << v } %}{{values}}{% end %}),
          %([1, 2, 3])
        )
      end
    end

    describe "path methods" do
      it "executes names" do
        assert_macro %({{x.names}}), %([String]), {x: Path.new("String")}
        assert_macro %({{x.names}}), %([Foo, Bar]), {x: Path.new("Foo", "Bar")}
      end

      it "executes global?" do
        assert_macro %({{x.global?}}), %(false), {x: Path.new("Foo")}
        assert_macro %({{x.global?}}), %(true), {x: Path.new("Foo", global: true)}
      end

      # TODO: remove deprecated tests
      it "executes global" do
        assert_macro %({{x.global}}), %(false), {x: Path.new("Foo")}
        assert_macro %({{x.global}}), %(true), {x: Path.new("Foo", global: true)}
      end

      it "executes resolve" do
        assert_macro %({{x.resolve}}), %(String), {x: Path.new("String")}

        assert_macro_error %({{x.resolve}}), "undefined constant Foo", {x: Path.new("Foo")}
      end

      it "executes resolve?" do
        assert_macro %({{x.resolve?}}), %(String), {x: Path.new("String")}
        assert_macro %({{x.resolve?}}), %(nil), {x: Path.new("Foo")}
      end

      it "executes types" do
        assert_macro %({{x.types}}), %([String]), {x: Path.new("String")}
      end
    end

    describe "annotation methods" do
      it "executes name" do
        assert_macro %({{x.name}}), %(Foo), {x: Annotation.new(Path.new("Foo"))}
        assert_macro %({{x.name}}), %(Foo::Bar), {x: Annotation.new(Path.new("Foo", "Bar"))}
      end

      it "executes [] with NumberLiteral" do
        assert_macro %({{x[y]}}), %(42), {
          x: Annotation.new(Path.new("Foo"), [42.int32] of ASTNode),
          y: 0.int32,
        }
      end

      it "executes [] with SymbolLiteral" do
        assert_macro %({{x[y]}}), %(42), {
          x: Annotation.new(Path.new("Foo"), [] of ASTNode, [NamedArgument.new("foo", 42.int32)]),
          y: "foo".symbol,
        }
      end

      it "executes [] with StringLiteral" do
        assert_macro %({{x[y]}}), %(42), {
          x: Annotation.new(Path.new("Foo"), [] of ASTNode, [NamedArgument.new("foo", 42.int32)]),
          y: "foo".string,
        }
      end

      it "executes [] with MacroId" do
        assert_macro %({{x[y]}}), %(42), {
          x: Annotation.new(Path.new("Foo"), [] of ASTNode, [NamedArgument.new("foo", 42.int32)]),
          y: MacroId.new("foo"),
        }
      end

      it "executes [] with other ASTNode, but raises an error" do
        assert_macro_error %({{x[y]}}), "argument to [] must be a number, symbol or string, not BoolLiteral", {
          x: Annotation.new(Path.new("Foo"), [] of ASTNode),
          y: true.bool,
        }
      end
    end

    describe ClassDef do
      class_def = ClassDef.new(Path.new("Foo"), abstract: true, superclass: Path.new("Parent"))
      struct_def = ClassDef.new(Path.new("Foo", "Bar", global: true), type_vars: %w(A B C D), splat_index: 2, struct: true, body: CharLiteral.new('a'))

      it "executes kind" do
        assert_macro %({{x.kind}}), %(class), {x: class_def}
        assert_macro %({{x.kind}}), %(struct), {x: struct_def}
      end

      it "executes name" do
        assert_macro %({{x.name}}), %(Foo), {x: class_def}
        assert_macro %({{x.name}}), %(::Foo::Bar(A, B, *C, D)), {x: struct_def}

        assert_macro %({{x.name(generic_args: true)}}), %(Foo), {x: class_def}
        assert_macro %({{x.name(generic_args: true)}}), %(::Foo::Bar(A, B, *C, D)), {x: struct_def}

        assert_macro %({{x.name(generic_args: false)}}), %(Foo), {x: class_def}
        assert_macro %({{x.name(generic_args: false)}}), %(::Foo::Bar), {x: struct_def}

        assert_macro_error %({{x.name(generic_args: 99)}}), "named argument 'generic_args' to ClassDef#name must be a BoolLiteral, not NumberLiteral", {x: class_def}
      end

      it "executes superclass" do
        assert_macro %({{x.superclass}}), %(Parent), {x: class_def}
        assert_macro %({{x.superclass}}), %(Parent(*T)), {x: ClassDef.new(Path.new("Foo"), superclass: Generic.new(Path.new("Parent"), [Splat.new(Path.new("T"))] of ASTNode))}
        assert_macro %({{x.superclass}}), %(), {x: struct_def}
      end

      it "executes type_vars" do
        assert_macro %({{x.type_vars}}), %([] of ::NoReturn), {x: class_def}
        assert_macro %({{x.type_vars}}), %([A, B, C, D]), {x: struct_def}
      end

      it "executes splat_index" do
        assert_macro %({{x.splat_index}}), %(nil), {x: class_def}
        assert_macro %({{x.splat_index}}), %(2), {x: struct_def}
      end

      it "executes body" do
        assert_macro %({{x.body}}), %(), {x: class_def}
        assert_macro %({{x.body}}), %('a'), {x: struct_def}
      end

      it "executes abstract?" do
        assert_macro %({{x.abstract?}}), %(true), {x: class_def}
        assert_macro %({{x.abstract?}}), %(false), {x: struct_def}
      end

      it "executes struct?" do
        assert_macro %({{x.struct?}}), %(false), {x: class_def}
        assert_macro %({{x.struct?}}), %(true), {x: struct_def}
      end
    end

    describe ModuleDef do
      module_def1 = ModuleDef.new(Path.new("Foo"))
      module_def2 = ModuleDef.new(Path.new("Foo", "Bar", global: true), type_vars: %w(A B C D), splat_index: 2, body: CharLiteral.new('a'))

      it "executes kind" do
        assert_macro %({{x.kind}}), %(module), {x: module_def1}
        assert_macro %({{x.kind}}), %(module), {x: module_def2}
      end

      it "executes name" do
        assert_macro %({{x.name}}), %(Foo), {x: module_def1}
        assert_macro %({{x.name}}), %(::Foo::Bar(A, B, *C, D)), {x: module_def2}

        assert_macro %({{x.name(generic_args: true)}}), %(Foo), {x: module_def1}
        assert_macro %({{x.name(generic_args: true)}}), %(::Foo::Bar(A, B, *C, D)), {x: module_def2}

        assert_macro %({{x.name(generic_args: false)}}), %(Foo), {x: module_def1}
        assert_macro %({{x.name(generic_args: false)}}), %(::Foo::Bar), {x: module_def2}

        assert_macro_error %({{x.name(generic_args: 99)}}), "named argument 'generic_args' to ModuleDef#name must be a BoolLiteral, not NumberLiteral", {x: module_def1}
      end

      it "executes type_vars" do
        assert_macro %({{x.type_vars}}), %([] of ::NoReturn), {x: module_def1}
        assert_macro %({{x.type_vars}}), %([A, B, C, D]), {x: module_def2}
      end

      it "executes splat_index" do
        assert_macro %({{x.splat_index}}), %(nil), {x: module_def1}
        assert_macro %({{x.splat_index}}), %(2), {x: module_def2}
      end

      it "executes body" do
        assert_macro %({{x.body}}), %(), {x: module_def1}
        assert_macro %({{x.body}}), %('a'), {x: module_def2}
      end
    end

    describe EnumDef do
      enum_def = EnumDef.new(Path.new("Foo", "Bar", global: true), [Path.new("X")] of ASTNode, Path.global("Int32"))

      it "executes kind" do
        assert_macro %({{x.kind}}), %(enum), {x: enum_def}
      end

      it "executes name" do
        assert_macro %({{x.name}}), %(::Foo::Bar), {x: enum_def}
        assert_macro %({{x.name(generic_args: true)}}), %(::Foo::Bar), {x: enum_def}
        assert_macro %({{x.name(generic_args: false)}}), %(::Foo::Bar), {x: enum_def}
        assert_macro_error %({{x.name(generic_args: 99)}}), "named argument 'generic_args' to EnumDef#name must be a BoolLiteral, not NumberLiteral", {x: enum_def}
      end

      it "executes base_type" do
        assert_macro %({{x.base_type}}), %(::Int32), {x: enum_def}
        assert_macro %({{x.base_type}}), %(), {x: EnumDef.new(Path.new("Baz"))}
      end

      it "executes body" do
        assert_macro %({{x.body}}), %(X), {x: enum_def}
      end
    end

    describe AnnotationDef do
      annotation_def = AnnotationDef.new(Path.new("Foo", "Bar", global: true))

      it "executes kind" do
        assert_macro %({{x.kind}}), %(annotation), {x: annotation_def}
      end

      it "executes name" do
        assert_macro %({{x.name}}), %(::Foo::Bar), {x: annotation_def}
        assert_macro %({{x.name(generic_args: true)}}), %(::Foo::Bar), {x: annotation_def}
        assert_macro %({{x.name(generic_args: false)}}), %(::Foo::Bar), {x: annotation_def}
        assert_macro_error %({{x.name(generic_args: 99)}}), "named argument 'generic_args' to AnnotationDef#name must be a BoolLiteral, not NumberLiteral", {x: annotation_def}
      end

      it "executes body" do
        assert_macro %({{x.body}}), %(), {x: annotation_def}
      end
    end

    describe LibDef do
      lib_def = LibDef.new(Path.new("Foo", "Bar", global: true), FunDef.new("foo"))

      it "executes kind" do
        assert_macro %({{x.kind}}), %(lib), {x: lib_def}
      end

      it "executes name" do
        assert_macro %({{x.name}}), %(::Foo::Bar), {x: lib_def}
        assert_macro %({{x.name(generic_args: true)}}), %(::Foo::Bar), {x: lib_def}
        assert_macro %({{x.name(generic_args: false)}}), %(::Foo::Bar), {x: lib_def}
        assert_macro_error %({{x.name(generic_args: 99)}}), "named argument 'generic_args' to LibDef#name must be a BoolLiteral, not NumberLiteral", {x: lib_def}
      end

      it "executes body" do
        assert_macro %({{x.body}}), %(fun foo), {x: lib_def}
      end
    end

    describe CStructOrUnionDef do
      c_struct_def = CStructOrUnionDef.new("Foo", TypeDeclaration.new("x".var, "Int".path))
      c_union_def = CStructOrUnionDef.new("Bar", Include.new("Foo".path), union: true)

      it "executes kind" do
        assert_macro %({{x.kind}}), %(struct), {x: c_struct_def}
        assert_macro %({{x.kind}}), %(union), {x: c_union_def}
      end

      it "executes name" do
        assert_macro %({{x.name}}), %(Foo), {x: c_struct_def}
        assert_macro %({{x.name(generic_args: true)}}), %(Foo), {x: c_struct_def}
        assert_macro %({{x.name(generic_args: false)}}), %(Foo), {x: c_struct_def}
        assert_macro_error %({{x.name(generic_args: 99)}}), "named argument 'generic_args' to CStructOrUnionDef#name must be a BoolLiteral, not NumberLiteral", {x: c_struct_def}
      end

      it "executes body" do
        assert_macro %({{x.body}}), %(x : Int), {x: c_struct_def}
        assert_macro %({{x.body}}), %(include Foo), {x: c_union_def}
      end

      it "executes union?" do
        assert_macro %({{x.union?}}), %(false), {x: c_struct_def}
        assert_macro %({{x.union?}}), %(true), {x: c_union_def}
      end
    end

    describe FunDef do
      lib_fun = FunDef.new("foo")
      top_level_fun = FunDef.new("bar", [Arg.new("x", restriction: "Int32".path), Arg.new("", restriction: "Char".path)], "Void".path, true, 1.int32, "y.z")
      top_level_fun2 = FunDef.new("baz", body: Nop.new)

      it "executes name" do
        assert_macro %({{x.name}}), %(foo), {x: lib_fun}
        assert_macro %({{x.name}}), %(bar), {x: top_level_fun}
      end

      it "executes real_name" do
        assert_macro %({{x.real_name}}), %(), {x: lib_fun}
        assert_macro %({{x.real_name}}), %("y.z"), {x: top_level_fun}
      end

      it "executes args" do
        assert_macro %({{x.args}}), %([]), {x: lib_fun}
        assert_macro %({{x.args}}), %([x : Int32,  : Char]), {x: top_level_fun}
      end

      it "executes variadic?" do
        assert_macro %({{x.variadic?}}), %(false), {x: lib_fun}
        assert_macro %({{x.variadic?}}), %(true), {x: top_level_fun}
      end

      it "executes return_type" do
        assert_macro %({{x.return_type}}), %(), {x: lib_fun}
        assert_macro %({{x.return_type}}), %(Void), {x: top_level_fun}
      end

      it "executes body" do
        assert_macro %({{x.body}}), %(), {x: lib_fun}
        assert_macro %({{x.body}}), %(1), {x: top_level_fun}
        assert_macro %({{x.body}}), %(), {x: top_level_fun2}
      end

      it "executes has_body?" do
        assert_macro %({{x.has_body?}}), %(false), {x: lib_fun}
        assert_macro %({{x.has_body?}}), %(true), {x: top_level_fun}
        assert_macro %({{x.has_body?}}), %(true), {x: top_level_fun2}
      end
    end

    describe TypeDef do
      type_def = TypeDef.new("Foo", Path.new("Bar", "Baz", global: true))

      it "executes name" do
        assert_macro %({{x.name}}), %(Foo), {x: type_def}
      end

      it "executes type" do
        assert_macro %({{x.type}}), %(::Bar::Baz), {x: type_def}
      end
    end

    describe ExternalVar do
      external_var1 = ExternalVar.new("foo", Path.new("Bar", "Baz"))
      external_var2 = ExternalVar.new("X", Generic.new(Path.global("Pointer"), ["Char".path] of ASTNode), real_name: "y.z")

      it "executes name" do
        assert_macro %({{x.name}}), %(foo), {x: external_var1}
        assert_macro %({{x.name}}), %(X), {x: external_var2}
      end

      it "executes real_name" do
        assert_macro %({{x.real_name}}), %(), {x: external_var1}
        assert_macro %({{x.real_name}}), %("y.z"), {x: external_var2}
      end

      it "executes type" do
        assert_macro %({{x.type}}), %(Bar::Baz), {x: external_var1}
        assert_macro %({{x.type}}), %(::Pointer(Char)), {x: external_var2}
      end
    end

    describe Asm do
      asm1 = Asm.new("nop")
      asm2 = Asm.new(
        text: "foo",
        outputs: [AsmOperand.new("=r", "x".var), AsmOperand.new("=r", "y".var)],
        inputs: [AsmOperand.new("i", 1.int32), AsmOperand.new("r", 2.int32)],
        clobbers: %w(rax memory),
        volatile: true,
        alignstack: true,
        intel: true,
        can_throw: true,
      )

      it "executes text" do
        assert_macro %({{x.text}}), %("nop"), {x: asm1}
        assert_macro %({{x.text}}), %("foo"), {x: asm2}
      end

      it "executes outputs" do
        assert_macro %({{x.outputs}}), %([] of ::NoReturn), {x: asm1}
        assert_macro %({{x.outputs}}), %(["=r"(x), "=r"(y)]), {x: asm2}
      end

      it "executes inputs" do
        assert_macro %({{x.inputs}}), %([] of ::NoReturn), {x: asm1}
        assert_macro %({{x.inputs}}), %(["i"(1), "r"(2)]), {x: asm2}
      end

      it "executes clobbers" do
        assert_macro %({{x.clobbers}}), %([] of ::NoReturn), {x: asm1}
        assert_macro %({{x.clobbers}}), %(["rax", "memory"]), {x: asm2}
      end

      it "executes volatile?" do
        assert_macro %({{x.volatile?}}), %(false), {x: asm1}
        assert_macro %({{x.volatile?}}), %(true), {x: asm2}
      end

      it "executes alignstack?" do
        assert_macro %({{x.alignstack?}}), %(false), {x: asm1}
        assert_macro %({{x.alignstack?}}), %(true), {x: asm2}
      end

      it "executes intel?" do
        assert_macro %({{x.intel?}}), %(false), {x: asm1}
        assert_macro %({{x.intel?}}), %(true), {x: asm2}
      end

      it "executes can_throw?" do
        assert_macro %({{x.can_throw?}}), %(false), {x: asm1}
        assert_macro %({{x.can_throw?}}), %(true), {x: asm2}
      end
    end

    describe AsmOperand do
      asm_operand1 = AsmOperand.new("=r", "x".var)
      asm_operand2 = AsmOperand.new("i", 1.int32)

      it "executes constraint" do
        assert_macro %({{x.constraint}}), %("=r"), {x: asm_operand1}
        assert_macro %({{x.constraint}}), %("i"), {x: asm_operand2}
      end

      it "executes exp" do
        assert_macro %({{x.exp}}), %(x), {x: asm_operand1}
        assert_macro %({{x.exp}}), %(1), {x: asm_operand2}
      end
    end

    describe "env" do
      it "has key" do
        with_env("FOO": "foo") do
          assert_macro %({{env("FOO")}}), %("foo")
        end
      end

      it "doesn't have key" do
        with_env("FOO": nil) do
          assert_macro %({{env("FOO")}}), %(nil)
        end
      end
    end

    describe "flag?" do
      it "has simple flag" do
        assert_macro %({{flag?(:foo)}}), %(true), flags: "foo"
      end

      it "doesn't have flag" do
        assert_macro %({{flag?(:foo)}}), %(false)
      end

      it "has flag value" do
        assert_macro %({{flag?(:foo)}}), %("bar"), flags: "foo=bar"
      end

      it "has empty flag value" do
        assert_macro %({{flag?(:foo)}}), %(""), flags: "foo="
      end

      it "uses last one of multiple values" do
        assert_macro %({{flag?(:foo)}}), %("baz"), flags: %w[foo=bar foo=baz]
        assert_macro %({{flag?(:foo)}}), %("bar"), flags: %w[foo=baz foo=bar]
      end

      describe "presents `name=value` as simple flag" do
        it "foo=bar" do
          assert_macro %({{flag?(:"foo=bar")}}), %(true), flags: "foo=bar"
        end

        it "foo=" do
          assert_macro %({{flag?(:foo=)}}), %(true), flags: "foo="
        end

        it "multiple values" do
          assert_macro %({{flag?(:"foo=bar")}}), %(true), flags: %w[foo=baz foo=bar]
          assert_macro %({{flag?(:"foo=baz")}}), %(true), flags: %w[foo=bar foo=baz]
          assert_macro %({{flag?(:"foo=bar")}}), %(true), flags: %w[foo=bar foo=baz]
          assert_macro %({{flag?(:"foo=baz")}}), %(true), flags: %w[foo=baz foo=bar]
        end

        it "multiple values and simple flag" do
          assert_macro %({{flag?(:"foo=bar")}}), %(true), flags: %w[foo=bar foo]
          assert_macro %({{flag?(:"foo=bar")}}), %(true), flags: %w[foo foo=bar]
        end
      end

      it "uses last one of multiple values and simple" do
        assert_macro %({{flag?(:foo)}}), %(true), flags: %w[foo=bar foo]
        assert_macro %({{flag?(:foo)}}), %("bar"), flags: %w[foo foo=bar]
      end
    end

    it "compares versions" do
      assert_macro %({{compare_versions("1.10.3", "1.2.3")}}), %(1)
    end

    describe "#warning" do
      it "emits a top level warning" do
        assert_warning <<-CRYSTAL, "Oh noes"
          macro test
            {% warning "Oh noes" %}
          end

          test
        CRYSTAL
      end
    end

    describe "#parse_type" do
      it "path" do
        assert_type(%[class Bar; end; {{ parse_type("Bar").is_a?(Path) ? 1 : 'a'}}]) { int32 }
        assert_type(%[class Bar; end; {{ parse_type(:Bar.id.stringify).is_a?(Path) ? 1 : 'a'}}]) { int32 }
      end

      it "generic" do
        assert_type(%[class Foo(A, B); end; {{ parse_type("Foo(Int32, String)").resolve.type_vars.size == 2 ? 1 : 'a' }}]) { int32 }
      end

      it "union - |" do
        assert_type(%[class Foo; end; class Bar; end; {{ parse_type("Foo|Bar").resolve.union_types.size == 2 ? 1 : 'a' }}]) { int32 }
      end

      it "union - Union" do
        assert_type(%[class Foo; end; class Bar; end; {{ parse_type("Union(Foo,Bar)").resolve.union_types.size == 2 ? 1 : 'a' }}]) { int32 }
      end

      it "union - in generic" do
        assert_type(%[{{ parse_type("Array(Int32 | String)").resolve.type_vars[0].union_types.size == 2 ? 1 : 'a' }}]) { int32 }
      end

      it "proc" do
        assert_type(%[{{ parse_type("String, Int32 -> Bool").inputs.size == 2 ? 1 : 'a' }}]) { int32 }
        assert_type(%[{{ parse_type("String, Int32 -> Bool").output.resolve == Bool ? 1 : 'a' }}]) { int32 }
      end

      it "metaclass" do
        assert_type(%[{{ parse_type("Int32.class").resolve == Int32.class ? 1 : 'a' }}]) { int32 }
        assert_type(%[{{ parse_type("Int32").resolve == Int32.instance ? 1 : 'a' }}]) { int32 }
      end

      it "raises on empty string" do
        expect_raises(Crystal::TypeException, "argument to parse_type cannot be an empty value") do
          assert_macro %({{parse_type ""}}), %(nil)
        end
      end

      it "raises on extra unparsed tokens before the type" do
        expect_raises(Crystal::TypeException, %(Invalid type name: "100Foo")) do
          assert_macro %({{parse_type "100Foo" }}), %(nil)
        end
      end

      it "raises on extra unparsed tokens after the type" do
        expect_raises(Crystal::TypeException, %(Invalid type name: "Foo(Int32)100")) do
          assert_macro %({{parse_type "Foo(Int32)100" }}), %(nil)
        end
      end

      it "raises on non StringLiteral arguments" do
        expect_raises(Crystal::TypeException, "argument to parse_type must be a StringLiteral, not SymbolLiteral") do
          assert_macro %({{parse_type :Foo }}), %(nil)
        end
      end

      it "exposes syntax warnings" do
        assert_warning %({% parse_type "Foo(0x8000_0000_0000_0000)" %}), "Warning: 0x8000_0000_0000_0000 doesn't fit in an Int64, try using the suffix u64 or i128"
      end
    end

    describe "printing" do
      it "puts" do
        String.build do |io|
          assert_macro(%({% puts foo %}), "") do |program|
            program.stdout = io
            {foo: "bar".string}
          end
        end.should eq %(bar\n)
      end

      it "print" do
        String.build do |io|
          assert_macro(%({% print foo %}), "") do |program|
            program.stdout = io
            {foo: "bar".string}
          end
        end.should eq %(bar)
      end

      it "p" do
        String.build do |io|
          assert_macro(%({% p foo %}), "") do |program|
            program.stdout = io
            {foo: "bar".string}
          end
        end.should eq %("bar"\n)
      end

      it "p!" do
        String.build do |io|
          assert_macro("{% p! foo %}", "") do |program|
            program.stdout = io
            {foo: "bar".string}
          end
        end.should eq %(foo # => "bar"\n)
      end

      it "pp" do
        String.build do |io|
          assert_macro("{% pp foo %}", "") do |program|
            program.stdout = io
            {foo: "bar".string}
          end
        end.should eq %("bar"\n)
      end

      it "pp!" do
        String.build do |io|
          assert_macro("{% pp! foo %}", "") do |program|
            program.stdout = io
            {foo: "bar".string}
          end
        end.should eq %(foo # => "bar"\n)
      end
    end
  end

  describe "file_exists?" do
    context "with absolute path" do
      it "returns true if file exists" do
        run(%q<
          {{file_exists?("#{__DIR__}/../data/build")}} ? 10 : 20
          >, filename: __FILE__).to_i.should eq(10)
      end

      it "returns false if file doesn't exist" do
        run(%q<
          {{file_exists?("#{__DIR__}/../data/build_foo")}} ? 10 : 20
          >, filename: __FILE__).to_i.should eq(20)
      end
    end

    context "with relative path" do
      it "reads file (exists)" do
        run(%q<
          {{file_exists?("spec/compiler/data/build")}} ? 10 : 20
          >, filename: __FILE__).to_i.should eq(10)
      end

      it "reads file (doesn't exist)" do
        run(%q<
          {{file_exists?("spec/compiler/data/build_foo")}} ? 10 : 20
          >, filename: __FILE__).to_i.should eq(20)
      end
    end
  end

  describe "read_file" do
    context "with absolute path" do
      it "reads file (exists)" do
        run(%q<
          {{read_file("#{__DIR__}/../data/build")}}
          >, filename: __FILE__).to_string.should eq(File.read("#{__DIR__}/../data/build"))
      end

      it "reads file (doesn't exist)" do
        assert_error <<-CRYSTAL,
          {{read_file("#{__DIR__}/../data/build_foo")}}
          CRYSTAL
          "Error opening file with mode 'r'"
      end
    end

    context "with relative path" do
      it "reads file (exists)" do
        run(%q<
          {{read_file("spec/compiler/data/build")}}
          >, filename: __FILE__).to_string.should eq(File.read("spec/compiler/data/build"))
      end

      it "reads file (doesn't exist)" do
        assert_error <<-CRYSTAL,
          {{read_file("spec/compiler/data/build_foo")}}
          CRYSTAL
          "Error opening file with mode 'r'"
      end
    end
  end

  describe "read_file?" do
    context "with absolute path" do
      it "reads file (doesn't exist)" do
        run(%q<
          {{read_file?("#{__DIR__}/../data/build_foo")}} ? 10 : 20
          >, filename: __FILE__).to_i.should eq(20)
      end
    end

    context "with relative path" do
      it "reads file (doesn't exist)" do
        run(%q<
          {{read_file?("spec/compiler/data/build_foo")}} ? 10 : 20
          >, filename: __FILE__).to_i.should eq(20)
      end
    end
  end

  describe ".system" do
    it "command does not exist" do
      assert_error %({{ `commanddoesnotexist` }}), "error executing command: commanddoesnotexist"
    end

    it "successful command" do
      assert_macro %({{ `#{exit_code_command(0)}` }}), ""
    end

    it "successful command with output" do
      assert_macro %({{ `#{shell_command("echo foobar")}` }}), "foobar#{newline}"
    end

    it "failing command" do
      assert_error %({{ `#{exit_code_command(1)}` }}), "error executing command: #{exit_code_command(1)}, got exit status 1"
      assert_error %({{ `#{exit_code_command(2)}` }}), "error executing command: #{exit_code_command(2)}, got exit status 2"
      assert_error %({{ `#{exit_code_command(127)}` }}), "error executing command: #{exit_code_command(127)}, got exit status 127"
    end
  end

  describe "error reporting" do
    it "reports wrong number of arguments" do
      assert_macro_error %({{[1, 2, 3].push}}), "wrong number of arguments for macro 'ArrayLiteral#push' (given 0, expected 1)"
    end

    it "reports wrong number of arguments, with optional parameters" do
      assert_macro_error %({{1.+(2, 3)}}), "wrong number of arguments for macro 'NumberLiteral#+' (given 2, expected 0..1)"
      assert_macro_error %({{[1][]}}), "wrong number of arguments for macro 'ArrayLiteral#[]' (given 0, expected 1..2)"
    end

    it "reports unexpected block" do
      assert_macro_error %({{[1, 2, 3].shuffle { |x| }}}), "macro 'ArrayLiteral#shuffle' is not expected to be invoked with a block, but a block was given"
    end

    it "reports missing block" do
      assert_macro_error %({{[1, 2, 3].reduce}}), "macro 'ArrayLiteral#reduce' is expected to be invoked with a block, but no block was given"
    end

    it "reports unexpected named argument" do
      assert_macro_error %({{"".starts_with?(other: "")}}), "no parameter named 'other'"
    end

    it "reports unexpected named argument (2)" do
      assert_macro_error %({{"".camelcase(foo: "")}}), "no parameter named 'foo'"
    end

    # there are no macro methods with required named parameters

    it "uses correct name for top-level macro methods" do
      assert_macro_error %({{flag?}}), "wrong number of arguments for macro '::flag?' (given 0, expected 1)"
    end
  end

  describe "immutability of returned container literals (#10818)" do
    it "Annotation#args" do
      node = Annotation.new(Path.new("Foo"), [42.int32, "a".string] of ASTNode)
      assert_macro %({{ (x.args << "a"; x.args.size) }}), "2", {x: node}
    end

    it "Generic#type_vars" do
      node = Generic.new("Foo".path, ["Bar".path, "Int32".path] of ASTNode)
      assert_macro %({{ (x.type_vars << "a"; x.type_vars.size) }}), "2", {x: node}
    end

    it "MultiAssign#targets" do
      node = MultiAssign.new(["foo".var, "bar".var] of ASTNode, [2.int32, "a".string] of ASTNode)
      assert_macro %({{ (x.targets << "a"; x.targets.size) }}), "2", {x: node}
    end

    it "MultiAssign#values" do
      node = MultiAssign.new(["foo".var, "bar".var] of ASTNode, [2.int32, "a".string] of ASTNode)
      assert_macro %({{ (x.values << "a"; x.values.size) }}), "2", {x: node}
    end

    it "ProcNotation#inputs" do
      node = ProcNotation.new([Path.new("SomeType"), Path.new("OtherType")] of ASTNode)
      assert_macro %({{ (x.inputs << "a"; x.inputs.size) }}), "2", {x: node}
    end

    it "ProcPointer#args" do
      node = ProcPointer.new(Var.new("some_object"), "method", [Path.new("SomeType"), Path.new("OtherType")] of ASTNode)
      assert_macro %({{ (x.args << "a"; x.args.size) }}), "2", {x: node}
    end

    it "StringInterpolation#expressions" do
      node = StringInterpolation.new(["fo".string, 1.int32, "o".string] of ASTNode)
      assert_macro %({{ (x.expressions << "a"; x.expressions.size) }}), "3", {x: node}
    end

    it "Union#types" do
      node = Crystal::Union.new(["Int32".path, "String".path] of ASTNode)
      assert_macro %({{ (x.types << "a"; x.types.size) }}), "2", {x: node}
    end

    it "When#conds" do
      node = When.new([2.int32, 3.int32] of ASTNode, 4.int32)
      assert_macro %({{ (x.conds << "a"; x.conds.size) }}), "2", {x: node}
    end
  end
end
