From d6f1c61e587cd638c41bf974e00550ef9910f154 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Fri, 4 May 2018 00:44:21 -0300 Subject: [PATCH 01/16] User-defined annotations --- spec/compiler/codegen/alias_spec.cr | 2 +- spec/compiler/codegen/macro_spec.cr | 21 ++ spec/compiler/formatter/formatter_spec.cr | 3 + spec/compiler/lexer/lexer_spec.cr | 3 +- spec/compiler/macro/macro_methods_spec.cr | 12 + spec/compiler/parser/parser_spec.cr | 22 +- spec/compiler/semantic/annotation_spec.cr | 241 ++++++++++++++ spec/compiler/semantic/class_var_spec.cr | 20 -- spec/compiler/semantic/lib_spec.cr | 19 +- spec/compiler/semantic/primitives_spec.cr | 2 +- src/compiler/crystal/codegen/codegen.cr | 2 +- src/compiler/crystal/codegen/link.cr | 47 ++- src/compiler/crystal/macros.cr | 28 ++ src/compiler/crystal/macros/methods.cr | 61 +++- src/compiler/crystal/program.cr | 20 +- src/compiler/crystal/semantic/ast.cr | 25 +- src/compiler/crystal/semantic/main_visitor.cr | 59 ++-- .../crystal/semantic/semantic_visitor.cr | 57 ++-- .../crystal/semantic/top_level_visitor.cr | 299 +++++++++--------- .../semantic/type_declaration_processor.cr | 31 +- .../semantic/type_declaration_visitor.cr | 21 +- .../crystal/semantic/type_guess_visitor.cr | 8 + src/compiler/crystal/syntax/ast.cr | 38 ++- src/compiler/crystal/syntax/lexer.cr | 4 + src/compiler/crystal/syntax/parser.cr | 49 ++- src/compiler/crystal/syntax/to_s.cr | 14 +- src/compiler/crystal/syntax/transformer.cr | 6 +- src/compiler/crystal/tools/doc/type.cr | 2 + src/compiler/crystal/tools/formatter.cr | 30 +- src/compiler/crystal/types.cr | 62 +++- src/enum.cr | 2 +- 31 files changed, 883 insertions(+), 327 deletions(-) create mode 100644 spec/compiler/semantic/annotation_spec.cr diff --git a/spec/compiler/codegen/alias_spec.cr b/spec/compiler/codegen/alias_spec.cr index 6ae1bce16c5f..3f595a4568ba 100644 --- a/spec/compiler/codegen/alias_spec.cr +++ b/spec/compiler/codegen/alias_spec.cr @@ -83,7 +83,7 @@ describe "Code gen: alias" do alias Foo = Moo end )) - result.program.link_attributes + result.program.link_annotations end it "doesn't crash on cast to as recursive alias (#639)" do diff --git a/spec/compiler/codegen/macro_spec.cr b/spec/compiler/codegen/macro_spec.cr index 3cc68b44d084..491911887953 100644 --- a/spec/compiler/codegen/macro_spec.cr +++ b/spec/compiler/codegen/macro_spec.cr @@ -1749,6 +1749,27 @@ describe "Code gen: macro" do )).to_i.should eq(10) end + it "gets default value of instance variable of inherited type that also includes module" do + run(%( + module Moo + @moo = 10 + end + + class Foo + include Moo + + def foo + {{ @type.instance_vars.first.default_value }} + end + end + + class Bar < Foo + end + + Bar.new.foo + )).to_i.should eq(10) + end + it "determines if variable has default value" do run(%( class Foo diff --git a/spec/compiler/formatter/formatter_spec.cr b/spec/compiler/formatter/formatter_spec.cr index 3d272e9e3556..b45822ff8065 100644 --- a/spec/compiler/formatter/formatter_spec.cr +++ b/spec/compiler/formatter/formatter_spec.cr @@ -1118,4 +1118,7 @@ describe Crystal::Formatter do assert_format "alias X = ((Y, Z) ->)" assert_format "def x(@y = ->(z) {})\nend" + + assert_format "class X; annotation FooAnnotation ; end ; end", "class X\n annotation FooAnnotation; end\nend" + assert_format "class X\n annotation FooAnnotation \n end \n end", "class X\n annotation FooAnnotation\n end\nend" end diff --git a/spec/compiler/lexer/lexer_spec.cr b/spec/compiler/lexer/lexer_spec.cr index 65dec562e81c..5da4aac3f560 100644 --- a/spec/compiler/lexer/lexer_spec.cr +++ b/spec/compiler/lexer/lexer_spec.cr @@ -145,7 +145,8 @@ describe "Lexer" do :begin, :lib, :fun, :type, :struct, :union, :enum, :macro, :out, :require, :case, :when, :select, :then, :of, :abstract, :rescue, :ensure, :is_a?, :alias, :pointerof, :sizeof, :instance_sizeof, :as, :as?, :typeof, :for, :in, - :with, :self, :super, :private, :protected, :asm, :uninitialized, :nil?] + :with, :self, :super, :private, :protected, :asm, :uninitialized, :nil?, + :annotation] it_lexes_idents ["ident", "something", "with_underscores", "with_1", "foo?", "bar!", "fooBar", "❨╯°□°❩╯︵┻━┻"] it_lexes_idents ["def?", "if?", "else?", "elsif?", "end?", "true?", "false?", "class?", "while?", diff --git a/spec/compiler/macro/macro_methods_spec.cr b/spec/compiler/macro/macro_methods_spec.cr index cf998ce48774..d763a2b8b696 100644 --- a/spec/compiler/macro/macro_methods_spec.cr +++ b/spec/compiler/macro/macro_methods_spec.cr @@ -1179,6 +1179,18 @@ describe "macro methods" do [TypeNode.new(program.reference)] of ASTNode end end + + it "executes nilable? (false)" do + assert_macro("x", "{{x.nilable?}}", "false") do |program| + [TypeNode.new(program.string)] of ASTNode + end + end + + it "executes nilable? (true)" do + assert_macro("x", "{{x.nilable?}}", "true") do |program| + [TypeNode.new(program.union_of(program.string, program.nil))] of ASTNode + end + end end describe "type declaration methods" do diff --git a/spec/compiler/parser/parser_spec.cr b/spec/compiler/parser/parser_spec.cr index 0fb1076bbcc4..c45260d0c6d9 100644 --- a/spec/compiler/parser/parser_spec.cr +++ b/spec/compiler/parser/parser_spec.cr @@ -1125,15 +1125,16 @@ describe "Parser" do it_parses "a = 1; class Foo; @x = a; end", [Assign.new("a".var, 1.int32), ClassDef.new("Foo".path, Assign.new("@x".instance_var, "a".call))] - it_parses "@[Foo]", Attribute.new("Foo") - it_parses "@[Foo()]", Attribute.new("Foo") - it_parses "@[Foo(1)]", Attribute.new("Foo", [1.int32] of ASTNode) - it_parses "@[Foo(\"hello\")]", Attribute.new("Foo", ["hello".string] of ASTNode) - it_parses "@[Foo(1, foo: 2)]", Attribute.new("Foo", [1.int32] of ASTNode, [NamedArgument.new("foo", 2.int32)]) - it_parses "@[Foo(1, foo: 2\n)]", Attribute.new("Foo", [1.int32] of ASTNode, [NamedArgument.new("foo", 2.int32)]) - it_parses "@[Foo(\n1, foo: 2\n)]", Attribute.new("Foo", [1.int32] of ASTNode, [NamedArgument.new("foo", 2.int32)]) + it_parses "@[Foo]", Annotation.new("Foo".path) + it_parses "@[Foo()]", Annotation.new("Foo".path) + it_parses "@[Foo(1)]", Annotation.new("Foo".path, [1.int32] of ASTNode) + it_parses "@[Foo(\"hello\")]", Annotation.new("Foo".path, ["hello".string] of ASTNode) + it_parses "@[Foo(1, foo: 2)]", Annotation.new("Foo".path, [1.int32] of ASTNode, [NamedArgument.new("foo", 2.int32)]) + it_parses "@[Foo(1, foo: 2\n)]", Annotation.new("Foo".path, [1.int32] of ASTNode, [NamedArgument.new("foo", 2.int32)]) + it_parses "@[Foo(\n1, foo: 2\n)]", Annotation.new("Foo".path, [1.int32] of ASTNode, [NamedArgument.new("foo", 2.int32)]) + it_parses "@[Foo::Bar]", Annotation.new(Path.new(["Foo", "Bar"])) - it_parses "lib LibC\n@[Bar]; end", LibDef.new("LibC", Attribute.new("Bar")) + it_parses "lib LibC\n@[Bar]; end", LibDef.new("LibC", Annotation.new("Bar".path)) it_parses "Foo(_)", Generic.new("Foo".path, [Underscore.new] of ASTNode) @@ -1679,6 +1680,11 @@ describe "Parser" do assert_syntax_error "<<-HEREDOC", "Unexpected EOF on heredoc identifier" assert_syntax_error "<<-HEREDOC\n", "Unterminated heredoc" + it_parses "annotation FooAnnotation; end", AnnotationDef.new("FooAnnotation".path) + it_parses "annotation FooAnnotation\n\nend", AnnotationDef.new("FooAnnotation".path) + it_parses "annotation Foo::BarAnnotation\n\nend", AnnotationDef.new(Path.new(["Foo", "BarAnnotation"])) + assert_syntax_error "annotation Foo", "annotation name must end with 'Annotation'" + it "gets corrects of ~" do node = Parser.parse("\n ~1") loc = node.location.not_nil! diff --git a/spec/compiler/semantic/annotation_spec.cr b/spec/compiler/semantic/annotation_spec.cr new file mode 100644 index 000000000000..0d4f912f48e3 --- /dev/null +++ b/spec/compiler/semantic/annotation_spec.cr @@ -0,0 +1,241 @@ +require "../../spec_helper" + +describe "Semantic: annotation" do + it "declares annotation" do + result = semantic(%( + annotation FooAnnotation + end + )) + + type = result.program.types["FooAnnotation"] + type.should be_a(AnnotationType) + type.name.should eq("FooAnnotation") + end + + it "can't find annotation in module" do + assert_type(%( + annotation FooAnnotation + end + + module Moo + end + + {% if Moo.annotation(FooAnnotation) %} + 1 + {% else %} + 'a' + {% end %} + )) { char } + end + + it "finds annotation in module" do + assert_type(%( + annotation FooAnnotation + end + + @[Foo] + module Moo + end + + {% if Moo.annotation(FooAnnotation) %} + 1 + {% else %} + 'a' + {% end %} + )) { int32 } + end + + it "uses annotation value, positional" do + assert_type(%( + annotation FooAnnotation + end + + @[Foo(1)] + module Moo + end + + {% if Moo.annotation(FooAnnotation)[0] == 1 %} + 1 + {% else %} + 'a' + {% end %} + )) { int32 } + end + + it "uses annotation value, keyword" do + assert_type(%( + annotation FooAnnotation + end + + @[Foo(x: 1)] + module Moo + end + + {% if Moo.annotation(FooAnnotation)[:x] == 1 %} + 1 + {% else %} + 'a' + {% end %} + )) { int32 } + end + + it "finds annotation in class" do + assert_type(%( + annotation FooAnnotation + end + + @[Foo] + class Moo + end + + {% if Moo.annotation(FooAnnotation) %} + 1 + {% else %} + 'a' + {% end %} + )) { int32 } + end + + it "finds annotation in struct" do + assert_type(%( + annotation FooAnnotation + end + + @[Foo] + struct Moo + end + + {% if Moo.annotation(FooAnnotation) %} + 1 + {% else %} + 'a' + {% end %} + )) { int32 } + end + + it "finds annotation in enum" do + assert_type(%( + annotation FooAnnotation + end + + @[Foo] + enum Moo + A = 1 + end + + {% if Moo.annotation(FooAnnotation) %} + 1 + {% else %} + 'a' + {% end %} + )) { int32 } + end + + it "finds annotation in lib" do + assert_type(%( + annotation FooAnnotation + end + + @[Foo] + lib Moo + A = 1 + end + + {% if Moo.annotation(FooAnnotation) %} + 1 + {% else %} + 'a' + {% end %} + )) { int32 } + end + + it "can't find annotation in instance var" do + assert_type(%( + annotation FooAnnotation + end + + class Moo + @x : Int32 = 1 + + def foo + {% if @type.instance_vars.first.annotation(FooAnnotation) %} + 1 + {% else %} + 'a' + {% end %} + end + end + + Moo.new.foo + )) { char } + end + + it "finds annotation in instance var (declaration)" do + assert_type(%( + annotation FooAnnotation + end + + class Moo + @[Foo] + @x : Int32 = 1 + + def foo + {% if @type.instance_vars.first.annotation(FooAnnotation) %} + 1 + {% else %} + 'a' + {% end %} + end + end + + Moo.new.foo + )) { int32 } + end + + it "finds annotation in instance var (assignment)" do + assert_type(%( + annotation FooAnnotation + end + + class Moo + @[Foo] + @x = 1 + + def foo + {% if @type.instance_vars.first.annotation(FooAnnotation) %} + 1 + {% else %} + 'a' + {% end %} + end + end + + Moo.new.foo + )) { int32 } + end + + it "finds annotation in instance var (declaration, generic)" do + assert_type(%( + annotation FooAnnotation + end + + class Moo(T) + @[Foo] + @x : T + + def initialize(@x : T) + end + + def foo + {% if @type.instance_vars.first.annotation(FooAnnotation) %} + 1 + {% else %} + 'a' + {% end %} + end + end + + Moo.new(1).foo + )) { int32 } + end +end diff --git a/spec/compiler/semantic/class_var_spec.cr b/spec/compiler/semantic/class_var_spec.cr index b59e2bdc954c..f11e3ad848ac 100644 --- a/spec/compiler/semantic/class_var_spec.cr +++ b/spec/compiler/semantic/class_var_spec.cr @@ -82,26 +82,6 @@ describe "Semantic: class var" do ") { int32 } end - it "says illegal attribute for class var" do - assert_error %( - class Foo - @[Foo] - @@foo - end - ), - "illegal attribute" - end - - it "says illegal attribute for class var assignment" do - assert_error %( - class Foo - @[Foo] - @@foo = 1 - end - ), - "illegal attribute" - end - it "allows self.class as type var in class body (#537)" do assert_type(%( class Bar(T) diff --git a/spec/compiler/semantic/lib_spec.cr b/spec/compiler/semantic/lib_spec.cr index f52b61c0da4a..9fcbe9c97043 100644 --- a/spec/compiler/semantic/lib_spec.cr +++ b/spec/compiler/semantic/lib_spec.cr @@ -285,15 +285,6 @@ describe "Semantic: lib" do )) { float64 } end - it "errors if applying wrong attribute" do - assert_error %( - @[Bar] - lib LibFoo - end - ), - "illegal attribute for lib, valid attributes are: Link" - end - it "errors if missing link arguments" do assert_error %( @[Link] @@ -438,7 +429,7 @@ describe "Semantic: lib" do LibSDL.init(0_u32) )) sdl = result.program.types["LibSDL"].as(LibType) - attrs = sdl.link_attributes.not_nil! + attrs = sdl.link_annotations.not_nil! attrs.size.should eq(2) attrs[0].lib.should eq("SDL") attrs[1].lib.should eq("SDLMain") @@ -499,7 +490,7 @@ describe "Semantic: lib" do LibSDL.init(0_u32) )) sdl = result.program.types["LibSDL"].as(LibType) - attrs = sdl.link_attributes.not_nil! + attrs = sdl.link_annotations.not_nil! attrs.size.should eq(2) attrs[0].lib.should eq("SDL") attrs[1].lib.should eq("SDLMain") @@ -519,7 +510,7 @@ describe "Semantic: lib" do LibSDL.init(0_u32) )) sdl = result.program.types["LibSDL"].as(LibType) - attrs = sdl.link_attributes.not_nil! + attrs = sdl.link_annotations.not_nil! attrs.size.should eq(1) attrs[0].lib.should eq("SDL") end @@ -536,7 +527,7 @@ describe "Semantic: lib" do LibSDL.init )) sdl = result.program.types["LibSDL"].as(LibType) - attrs = sdl.link_attributes.not_nil! + attrs = sdl.link_annotations.not_nil! attrs.size.should eq(1) attrs[0].lib.should eq("SDL") end @@ -905,7 +896,7 @@ describe "Semantic: lib" do fun foo : Int32 end ), - "wrong number of arguments for attribute CallConvention (given 2, expected 1)" + "wrong number of arguments for annotation CallConvention (given 2, expected 1)" end it "errors if CallConvention argument is not a string" do diff --git a/spec/compiler/semantic/primitives_spec.cr b/spec/compiler/semantic/primitives_spec.cr index a5048e456cf7..2fe4b937e814 100644 --- a/spec/compiler/semantic/primitives_spec.cr +++ b/spec/compiler/semantic/primitives_spec.cr @@ -214,7 +214,7 @@ describe "Semantic: primitives" do end end ), - "expected Primitive attribute to have one argument" + "expected Primitive annotation to have one argument" end it "errors if @[Primitive] has non-symbol arg" do diff --git a/src/compiler/crystal/codegen/codegen.cr b/src/compiler/crystal/codegen/codegen.cr index 25287dad1723..f015688f9326 100644 --- a/src/compiler/crystal/codegen/codegen.cr +++ b/src/compiler/crystal/codegen/codegen.cr @@ -342,7 +342,7 @@ module Crystal end end - def visit(node : Attribute) + def visit(node : Annotation) false end diff --git a/src/compiler/crystal/codegen/link.cr b/src/compiler/crystal/codegen/link.cr index df1b5d84ccc6..7ae4f2f3aa16 100644 --- a/src/compiler/crystal/codegen/link.cr +++ b/src/compiler/crystal/codegen/link.cr @@ -1,5 +1,5 @@ module Crystal - struct LinkAttribute + struct LinkAnnotation getter lib : String? getter ldflags : String? getter framework : String? @@ -11,17 +11,12 @@ module Crystal @static end - def self.from(attr : ASTNode) - name = attr.name - args = attr.args - named_args = attr.named_args - - if name != "Link" - attr.raise "illegal attribute for lib, valid attributes are: Link" - end + def self.from(ann : Annotation) + args = ann.args + named_args = ann.named_args if args.empty? && !named_args - attr.raise "missing link arguments: must at least specify a library name" + ann.raise "missing link arguments: must at least specify a library name" end lib_name = nil @@ -45,7 +40,7 @@ module Crystal arg.raise "'framework' link argument must be a String" unless arg.is_a?(StringLiteral) lib_framework = arg.value else - attr.wrong_number_of "link arguments", args.size, "1..4" + ann.wrong_number_of "link arguments", args.size, "1..4" end count += 1 @@ -87,12 +82,12 @@ module Crystal private def lib_flags_windows String.build do |flags| - link_attributes.reverse_each do |attr| - if ldflags = attr.ldflags + link_annotations.reverse_each do |ann| + if ldflags = ann.ldflags flags << ' ' << ldflags end - if libname = attr.lib + if libname = ann.lib flags << ' ' << libname << ".lib" end end @@ -104,17 +99,17 @@ module Crystal has_pkg_config = nil String.build do |flags| - link_attributes.reverse_each do |attr| - if ldflags = attr.ldflags + link_annotations.reverse_each do |ann| + if ldflags = ann.ldflags flags << ' ' << ldflags end - if libname = attr.lib + if libname = ann.lib if has_pkg_config.nil? has_pkg_config = Process.run("which", {"pkg-config"}, output: Process::Redirect::Close).success? end - static = has_flag?("static") || attr.static? + static = has_flag?("static") || ann.static? if has_pkg_config && (libflags = pkg_config_flags(libname, static, library_path)) flags << ' ' << libflags @@ -125,7 +120,7 @@ module Crystal end end - if framework = attr.framework + if framework = ann.framework flags << " -framework " << framework end end @@ -138,10 +133,10 @@ module Crystal end end - def link_attributes - attrs = [] of LinkAttribute - add_link_attributes @types, attrs - attrs + def link_annotations + annotations = [] of LinkAnnotation + add_link_annotations @types, annotations + annotations end private def pkg_config_flags(libname, static, library_path) @@ -172,15 +167,15 @@ module Crystal nil end - private def add_link_attributes(types, attrs) + private def add_link_annotations(types, attrs) types.try &.each_value do |type| next if type.is_a?(AliasType) || type.is_a?(TypeDefType) - if type.is_a?(LibType) && type.used? && (link_attrs = type.link_attributes) + if type.is_a?(LibType) && type.used? && (link_attrs = type.link_annotations) attrs.concat link_attrs end - add_link_attributes type.types?, attrs + add_link_annotations type.types?, attrs end end end diff --git a/src/compiler/crystal/macros.cr b/src/compiler/crystal/macros.cr index 31e17dbf8b2c..4b75bbe066bd 100644 --- a/src/compiler/crystal/macros.cr +++ b/src/compiler/crystal/macros.cr @@ -799,6 +799,25 @@ module Crystal::Macros # can in turn be `nil`). def has_default_value? : BoolLiteral end + + # Returns any `Annotation` with the given `type` + # attached to this variable. + def annotation(type : TypeNode) : Annotation + end + end + + # An annotation on top of a type or variable. + class Annotation < ASTNode + # Returns the value of a positional argument, + # or NilLiteral if out of bounds. + def [](index : NumberLiteral) : ASTNode + end + + # Returns the value of a named argument, + # or NilLiteral if the named argument isn't + # used in this attribute. + def [](name : SymbolLiteral) : ASTNode + end end # A local variable or block argument. @@ -1557,6 +1576,10 @@ module Crystal::Macros def union? : BoolLiteral end + # Returns `true` if this type is nilable (if it has `Nil` amongst its types). + def nilable? : BoolLiteral + end + # Returns the types comforming a union type, if this is a union type. # Gives a compile error otherwise. # @@ -1628,6 +1651,11 @@ module Crystal::Macros def has_attribute?(name : StringLiteral | SymbolLiteral) : BoolLiteral end + # Returns any `Annotation` with the given `type` + # attached to this type. + def annotation(type : TypeNode) : Annotation + end + # Returns the number of elements in this tuple type or tuple metaclass type. # Gives a compile error if this is not one of those types. def size : NumberLiteral diff --git a/src/compiler/crystal/macros/methods.cr b/src/compiler/crystal/macros/methods.cr index 1ae7cb97e623..f63c9afad780 100644 --- a/src/compiler/crystal/macros/methods.cr +++ b/src/compiler/crystal/macros/methods.cr @@ -1069,6 +1069,20 @@ module Crystal interpret_argless_method(method, args) do BoolLiteral.new(!!default_value) end + when "annotation" + interpret_one_arg_method(method, args) do |arg| + unless arg.is_a?(TypeNode) + args[0].raise "argument to 'MetaVar#annotation' must be a TypeNode, not #{arg.class_desc}'" + end + + type = arg.type + unless type.is_a?(AnnotationType) + args[0].raise "argument to 'MetaVar#annotation' must be an annotation type , not #{type} (#{type.type_desc})'" + end + + value = self.var.annotation(type) + value || NilLiteral.new + end else super end @@ -1407,6 +1421,8 @@ module Crystal interpret_argless_method(method, args) { BoolLiteral.new(type.abstract?) } when "union?" interpret_argless_method(method, args) { BoolLiteral.new(type.is_a?(UnionType)) } + when "nilable?" + interpret_argless_method(method, args) { BoolLiteral.new(type.nilable?) } when "union_types" interpret_argless_method(method, args) { TypeNode.union_types(type) } when "name" @@ -1447,6 +1463,20 @@ module Crystal value = arg.to_string("argument to 'TypeNode#has_attribute?'") BoolLiteral.new(!!type.has_attribute?(value)) end + when "annotation" + interpret_one_arg_method(method, args) do |arg| + unless arg.is_a?(TypeNode) + args[0].raise "argument to 'TypeNode#annotation' must be a TypeNode, not #{arg.class_desc}'" + end + + type = arg.type + unless type.is_a?(AnnotationType) + args[0].raise "argument to 'TypeNode#annotation' must be an annotation type , not #{type} (#{type.type_desc})'" + end + + value = self.type.annotation(type) + value || NilLiteral.new + end when "size" interpret_argless_method(method, args) do type = self.type.instance_type @@ -1580,13 +1610,10 @@ module Crystal def self.instance_vars(type) if type.is_a?(InstanceVarContainer) - if type.is_a?(InstanceVarInitializerContainer) - initializers = type.instance_vars_initializers - end - ArrayLiteral.map(type.all_instance_vars) do |name, ivar| meta_var = MetaMacroVar.new(name[1..-1], ivar.type) - meta_var.default_value = initializers.try &.find { |init| init.name == name }.try &.value + meta_var.var = ivar + meta_var.default_value = type.get_instance_var_initializer(name).try(&.value) meta_var end else @@ -1960,6 +1987,30 @@ module Crystal end end end + + class Annotation + def interpret(method, args, block, interpreter) + case method + when "[]" + interpret_one_arg_method(method, args) do |arg| + case arg + when NumberLiteral + index = arg.to_number.to_i + self.args[index]? || NilLiteral.new + when SymbolLiteral + named_arg = self.named_args.try &.find do |named_arg| + named_arg.name == arg.value + end + named_arg.try(&.value) || NilLiteral.new + else + raise "argument to 'Annotation#[]' must be integer or symbol, not #{arg.class_desc}" + end + end + else + super + end + end + end end private def intepret_array_or_tuple_method(object, klass, method, args, block, interpreter) diff --git a/src/compiler/crystal/program.cr b/src/compiler/crystal/program.cr index 3e5341c40814..ec9750cc472a 100644 --- a/src/compiler/crystal/program.cr +++ b/src/compiler/crystal/program.cr @@ -211,6 +211,20 @@ module Crystal types["GC"] = gc = NonGenericModuleType.new self, self, "GC" gc.metaclass.as(ModuleType).add_def Def.new("add_finalizer", [Arg.new("object")], Nop.new) + # Built-in annotations + types["AlwaysInlineAnnotation"] = @always_inline_annotation = AnnotationType.new self, self, "AlwaysInlineAnnotation" + types["CallConventionAnnotation"] = @call_convention_annotation = AnnotationType.new self, self, "CallConventionAnnotation" + types["ExternAnnotation"] = @extern_annotation = AnnotationType.new self, self, "ExternAnnotation" + types["FlagsAnnotation"] = @flags_annotation = AnnotationType.new self, self, "FlagsAnnotation" + types["LinkAnnotation"] = @link_annotation = AnnotationType.new self, self, "LinkAnnotation" + types["NakedAnnotation"] = @naked_annotation = AnnotationType.new self, self, "NakedAnnotation" + types["NoInlineAnnotation"] = @no_inline_annotation = AnnotationType.new self, self, "NoInlineAnnotation" + types["PackedAnnotation"] = @packed_annotation = AnnotationType.new self, self, "PackedAnnotation" + types["PrimitiveAnnotation"] = @primitive_annotation = AnnotationType.new self, self, "PrimitiveAnnotation" + types["RaisesAnnotation"] = @raises_annotation = AnnotationType.new self, self, "RaisesAnnotation" + types["ReturnsTwiceAnnotation"] = @returns_twice_annotation = AnnotationType.new self, self, "ReturnsTwiceAnnotation" + types["ThreadLocalAnnotation"] = @thread_local_annotation = AnnotationType.new self, self, "ThreadLocalAnnotation" + define_crystal_constants end @@ -430,7 +444,11 @@ module Crystal {% for name in %w(object no_return value number reference void nil bool char int int8 int16 int32 int64 int128 uint8 uint16 uint32 uint64 uint128 float float32 float64 string symbol pointer array static_array - exception tuple named_tuple proc union enum range regex crystal) %} + exception tuple named_tuple proc union enum range regex crystal + packed_annotation thread_local_annotation no_inline_annotation + always_inline_annotation naked_annotation returns_twice_annotation + raises_annotation primitive_annotation call_convention_annotation + flags_annotation link_annotation extern_annotation) %} def {{name.id}} @{{name.id}}.not_nil! end diff --git a/src/compiler/crystal/semantic/ast.cr b/src/compiler/crystal/semantic/ast.cr index 102eb8b49b38..a93d3ae3baa1 100644 --- a/src/compiler/crystal/semantic/ast.cr +++ b/src/compiler/crystal/semantic/ast.cr @@ -124,16 +124,16 @@ module Crystal property? self_closured = false property? captured_block = false - # `true` if this def has the `@[NoInline]` attribute + # `true` if this def has the `@[NoInline]` annotation property? no_inline = false - # `true` if this def has the `@[AlwaysInline]` attribute + # `true` if this def has the `@[AlwaysInline]` annotation property? always_inline = false - # `true` if this def has the `@[ReturnsTwice]` attribute + # `true` if this def has the `@[ReturnsTwice]` annotation property? returns_twice = false - # `true` if this def has the `@[Naked]` attribute + # `true` if this def has the `@[Naked]` annotation property? naked = false # Is this a `new` method that was expanded from an initialize? @@ -481,6 +481,9 @@ module Crystal # Is this variable "unsafe" (no need to check if it was initialized)? property? uninitialized = false + # Annotations of this instance var + property annotations : Hash(AnnotationType, Annotation)? + def kind case name[0] when '@' @@ -497,6 +500,17 @@ module Crystal def global? kind == :global end + + # Adds an annotation with the given type and value + def add_annotation(annotation_type : AnnotationType, value : Annotation) + annotations = @annotations ||= {} of AnnotationType => Annotation + annotations[annotation_type] = value + end + + # Returns the annotation with the given type, if any, or nil otherwise + def annotation(annotation_type) : Annotation? + @annotations.try &.[annotation_type] + end end class ClassVar @@ -723,6 +737,9 @@ module Crystal property name : String property default_value : ASTNode? + # The instance variable associated with this meta macro var + property! var : MetaTypeVar + def initialize(@name, @type) end diff --git a/src/compiler/crystal/semantic/main_visitor.cr b/src/compiler/crystal/semantic/main_visitor.cr index f43974280f6d..a2180db9f5fe 100644 --- a/src/compiler/crystal/semantic/main_visitor.cr +++ b/src/compiler/crystal/semantic/main_visitor.cr @@ -36,8 +36,8 @@ module Crystal # # Call resolution logic is in `Call#recalculate`, where method lookup is done. class MainVisitor < SemanticVisitor - ValidGlobalAttributes = %w(ThreadLocal) - ValidClassVarAttributes = %w(ThreadLocal) + ValidGlobalAnnotations = %w(ThreadLocal) + ValidClassVarAnnotations = %w(ThreadLocal) getter! typed_def property! untyped_def : Def @@ -415,19 +415,25 @@ module Crystal node.raise "declaring the type of a class variable must be done at the class level" end - attributes = check_valid_attributes node, ValidClassVarAttributes, "class variable" + thread_local = false + process_annotations do |ann| + thread_local = true if ann == @program.thread_local_annotation + end + class_var = lookup_class_var(var) var.var = class_var - if Attribute.any?(attributes, "ThreadLocal") - class_var.thread_local = true - end + class_var.thread_local = true if thread_local when Global if @untyped_def node.raise "declaring the type of a global variable must be done at the class level" end - attributes = check_valid_attributes node, ValidGlobalAttributes, "global variable" - if Attribute.any?(attributes, "ThreadLocal") + thread_local = false + process_annotations do |ann| + thread_local = true if ann == @program.thread_local_annotation + end + + if thread_local global_var = @program.global_vars[var.name] global_var.thread_local = true end @@ -511,10 +517,13 @@ module Crystal node.raise "can only declare instance variables of a non-generic class, not a #{type.type_desc} (#{type})" end when ClassVar - attributes = check_valid_attributes node, ValidGlobalAttributes, "global variable" + thread_local = false + process_annotations do |ann| + thread_local = true if ann == @program.thread_local_annotation + end class_var = visit_class_var var - class_var.thread_local = true if Attribute.any?(attributes, "ThreadLocal") + class_var.thread_local = true if thread_local end node.type = @program.nil unless node.type? @@ -678,10 +687,13 @@ module Crystal end def visit(node : ClassVar) - attributes = check_valid_attributes node, ValidGlobalAttributes, "global variable" + thread_local = false + process_annotations do |ann| + thread_local = true if ann == @program.thread_local_annotation + end var = visit_class_var node - var.thread_local = true if Attribute.any?(attributes, "ThreadLocal") + var.thread_local = true if thread_local false end @@ -872,7 +884,10 @@ module Crystal end def type_assign(target : Global, value, node) - attributes = check_valid_attributes target, ValidGlobalAttributes, "global variable" + thread_local = false + process_annotations do |ann| + thread_local = true if ann == @program.thread_local_annotation + end value.accept self @@ -885,7 +900,7 @@ module Crystal var.bind_to program.nil_var end - var.thread_local = true if Attribute.any?(attributes, "ThreadLocal") + var.thread_local = true if thread_local target.var = var target.bind_to var @@ -895,21 +910,26 @@ module Crystal end def type_assign(target : ClassVar, value, node) - attributes = check_valid_attributes target, ValidClassVarAttributes, "class variable" + thread_local = false + process_annotations do |ann| + thread_local = true if ann == @program.thread_local_annotation + end # Outside a def is already handled by ClassVarsInitializerVisitor # (@exp_nest is 1 if we are at the top level because it was incremented # by one since we are inside an Assign) if !@typed_def && (@exp_nest <= 1) && !inside_block? var = lookup_class_var(target) - check_class_var_is_thread_local(target, var, attributes) + target.var = var + var.thread_local = true if thread_local return end value.accept self var = lookup_class_var(target) - check_class_var_is_thread_local(target, var, attributes) + target.var = var + var.thread_local = true if thread_local target.bind_to var @@ -917,11 +937,6 @@ module Crystal var.bind_to value end - def check_class_var_is_thread_local(target, var, attributes) - var.thread_local = true if Attribute.any?(attributes, "ThreadLocal") - target.var = var - end - def type_assign(target : Underscore, value, node) value.accept self node.bind_to value diff --git a/src/compiler/crystal/semantic/semantic_visitor.cr b/src/compiler/crystal/semantic/semantic_visitor.cr index 42327be678cb..56a2472099ee 100644 --- a/src/compiler/crystal/semantic/semantic_visitor.cr +++ b/src/compiler/crystal/semantic/semantic_visitor.cr @@ -93,6 +93,12 @@ abstract class Crystal::SemanticVisitor < Crystal::Visitor false end + def visit(node : AnnotationDef) + check_outside_exp node, "declare annotation" + node.set_type(@program.nil) + false + end + def visit(node : EnumDef) check_outside_exp node, "declare enum" pushing_type(node.resolved_type) do @@ -141,9 +147,9 @@ abstract class Crystal::SemanticVisitor < Crystal::Visitor false end - def visit(node : Attribute) - attributes = @attributes ||= [] of Attribute - attributes << node + def visit(node : Annotation) + annotations = @annotations ||= [] of Annotation + annotations << node false end @@ -183,23 +189,23 @@ abstract class Crystal::SemanticVisitor < Crystal::Visitor def end_visit_any(node) @exp_nest -= 1 if nesting_exp?(node) - if @attributes + if @annotations case node when Expressions # Nothing, will be taken care in individual expressions - when Attribute + when Annotation # Nothing when Nop # Nothing (might happen as a result of an evaulated macro if) when Call - # Don't clear attributes if these were generated by a macro + # Don't clear annotations if these were generated by a macro unless node.expanded - @attributes = nil + @annotations = nil end when MacroExpression, MacroIf, MacroFor - # Don't clear attributes if these were generated by a macro + # Don't clear annotations if these were generated by a macro else - @attributes = nil + @annotations = nil end end end @@ -213,7 +219,7 @@ abstract class Crystal::SemanticVisitor < Crystal::Visitor case node when Expressions, LibDef, CStructOrUnionDef, ClassDef, ModuleDef, FunDef, Def, Macro, Alias, Include, Extend, EnumDef, VisibilityModifier, MacroFor, MacroIf, MacroExpression, - FileNode, TypeDeclaration, Require + FileNode, TypeDeclaration, Require, AnnotationDef false else true @@ -406,23 +412,28 @@ abstract class Crystal::SemanticVisitor < Crystal::Visitor node.raise "expanding macro", ex end - def check_valid_attributes(node, valid_attributes, desc) - attributes = @attributes - return unless attributes + def process_annotations(annotations = @annotations) + annotations.try &.each do |ann| + yield lookup_annotation(ann), ann + end + end - attributes.each do |attr| - unless valid_attributes.includes?(attr.name) - attr.raise "illegal attribute for #{desc}, valid attributes are: #{valid_attributes.join ", "}" - end + def lookup_annotation(ann) + # For `@[Foo]` we actually search an annotation named `FooAnnotation` + path = ann.path.clone + path.names[-1] = "#{path.names.last}Annotation" - if attr.name != "Primitive" - if !attr.args.empty? || attr.named_args - attr.raise "#{attr.name} attribute can't receive arguments" - end - end + type = lookup_type(path) + + unless type + ann.raise "undefined annotation #{path}" end - attributes + unless type.is_a?(AnnotationType) + ann.raise "#{path} is not an annotation, it's a #{type.type_desc}" + end + + type end def check_allowed_in_lib(node, type = node.type.instance_type) diff --git a/src/compiler/crystal/semantic/top_level_visitor.cr b/src/compiler/crystal/semantic/top_level_visitor.cr index db0b4a51a014..8cbadb85631e 100644 --- a/src/compiler/crystal/semantic/top_level_visitor.cr +++ b/src/compiler/crystal/semantic/top_level_visitor.cr @@ -18,7 +18,7 @@ require "./semantic_visitor" # Macro calls are expanded, but only the first pass is done to them. This # allows macros to define new classes and methods. # -# We also process @[Link] attributes. +# We also process @[Link] annotations. # # After this pass we have completely defined the whole class hierarchy, # including methods. After this point no new classes or methods can be introduced @@ -30,11 +30,6 @@ require "./semantic_visitor" # subclasses or not and we can tag it as "virtual" (having subclasses), but that concept # might disappear in the future and we'll make consider everything as "maybe virtual". class Crystal::TopLevelVisitor < Crystal::SemanticVisitor - ValidDefAttributes = %w(AlwaysInline Naked NoInline Raises ReturnsTwice Primitive) - ValidFunDefAttributes = %w(AlwaysInline Naked NoInline Raises ReturnsTwice CallConvention) - ValidStructDefAttributes = %w(Packed) - ValidEnumDefAttributes = %w(Flags) - # These are `new` methods (expanded) that was created from `initialize` methods (original) getter new_expansions = [] of {original: Def, expanded: Def} @@ -51,18 +46,9 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor scope, name, type = lookup_type_def(node) - created_new_type = false - extern = false - extern_union = false - packed = false + annotations = @annotations - if node.struct? - extern, extern_union, packed = process_class_def_struct_attributes - else - if (attributes = @attributes) && !attributes.empty? - node.raise "class declaration can't have attributes" - end - end + created_new_type = false if type type = type.remove_alias @@ -89,25 +75,13 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor node.raise "#{name} is not a generic #{type.type_desc}" end end - - if extern && type.is_a?(NonGenericClassType) - type.extern = true - type.extern_union = extern_union - type.packed = packed - end else created_new_type = true if type_vars = node.type_vars type = GenericClassType.new @program, scope, name, nil, type_vars, false type.splat_index = node.splat_index - if extern - node.raise "can only use Extern attribute with non-generic structs" - end else type = NonGenericClassType.new @program, scope, name, nil, false - type.extern = extern - type.extern_union = extern_union - type.packed = packed end type.abstract = node.abstract? type.struct = node.struct? @@ -181,6 +155,41 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor scope.types[name] = type node.resolved_type = type + process_annotations(annotations) do |annotation_type, ann| + if node.struct? && type.is_a?(NonGenericClassType) + case annotation_type + when @program.extern_annotation + unless type.is_a?(NonGenericClassType) + node.raise "can only use Extern annotation with non-generic structs" + end + + unless ann.args.empty? + ann.raise "Extern annotation can't have positional arguments, only named arguments: 'union'" + end + + ann.named_args.try &.each do |named_arg| + case named_arg.name + when "union" + value = named_arg.value + if value.is_a?(BoolLiteral) + type.extern_union = value.value + else + value.raise "Extern 'union' annotation must be a boolean, not #{value.class_desc}" + end + else + named_arg.raise "unknown Extern named argument, valid arguments are: 'union'" + end + end + + type.extern = true + when @program.packed_annotation + type.packed = true + end + end + + type.add_annotation(annotation_type, ann) + end + attach_doc type, node pushing_type(type) do @@ -198,6 +207,8 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor def visit(node : ModuleDef) check_outside_exp node, "declare module" + annotations = @annotations + scope, name, type = lookup_type_def(node) if type @@ -224,6 +235,10 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor attach_doc type, node + process_annotations(annotations) do |annotation_type, ann| + type.add_annotation(annotation_type, ann) + end + pushing_type(type) do node.body.accept self end @@ -231,6 +246,25 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor false end + def visit(node : AnnotationDef) + check_outside_exp node, "declare annotation" + + scope, name, type = lookup_type_def(node) + + if type + unless type.is_a?(AnnotationType) + node.raise "#{type} is not an annotation, it's a #{type.type_desc}" + end + else + type = AnnotationType.new(@program, scope, name) + scope.types[name] = type + end + + attach_doc type, node + + false + end + def visit(node : Alias) check_outside_exp node, "declare alias" @@ -277,8 +311,15 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor def visit(node : Def) check_outside_exp node, "declare def" - attributes = check_valid_attributes node, ValidDefAttributes, "def" - node.doc ||= attributes_doc() + annotations = @annotations + + process_def_annotations(node) do |annotation_type, ann| + if annotation_type == @program.primitive_annotation + process_primitive_annotation(node, ann) + end + end + + node.doc ||= annotations_doc() check_ditto node is_instance_method = false @@ -308,8 +349,6 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor target_type = target_type.as(ModuleType) - process_def_attributes node, attributes - if node.abstract? if (target_type.class? || target_type.struct?) && !target_type.abstract? node.raise "can't define abstract def on non-abstract #{target_type.type_desc}" @@ -319,11 +358,6 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor end end - primitive_attribute = attributes.try &.find { |attr| attr.name == "Primitive" } - if primitive_attribute - process_primitive_attribute(node, primitive_attribute) - end - if target_type.struct? && !target_type.metaclass? && node.name == "finalize" node.raise "structs can't have finalizers because they are not tracked by the GC" end @@ -355,12 +389,12 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor false end - private def process_primitive_attribute(node, attribute) - if attribute.args.size != 1 - attribute.raise "expected Primitive attribute to have one argument" + private def process_primitive_annotation(node, ann) + if ann.args.size != 1 + ann.raise "expected Primitive annotation to have one argument" end - arg = attribute.args.first + arg = ann.args.first unless arg.is_a?(SymbolLiteral) arg.raise "expected Primitive argument to be a symbol literal" end @@ -392,7 +426,7 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor def visit(node : LibDef) check_outside_exp node, "declare lib" - link_attributes, call_convention = process_lib_attributes + annotations = @annotations scope = current_type_scope(node) @@ -406,8 +440,16 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor node.resolved_type = type type.private = true if node.visibility.private? - type.add_link_attributes(link_attributes) - type.call_convention = call_convention if call_convention + + process_annotations(annotations) do |annotation_type, ann| + case annotation_type + when @program.link_annotation + type.add_link_annotation(LinkAnnotation.from(ann)) + when @program.call_convention_annotation + type.call_convention = parse_call_convention(ann, type.call_convention) + end + type.add_annotation(annotation_type, ann) + end pushing_type(type) do @in_lib = true @@ -419,8 +461,12 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor end def visit(node : CStructOrUnionDef) + packed = false + unless node.union? - attributes = check_valid_attributes node, ValidStructDefAttributes, "struct" + process_annotations do |ann| + packed = true if ann == @program.packed_annotation + end end type = current_type.types[node.name]? @@ -449,7 +495,7 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor node.resolved_type = type - type.packed = true if Attribute.any?(attributes, "Packed") + type.packed = packed false end @@ -468,8 +514,9 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor def visit(node : EnumDef) check_outside_exp node, "declare enum" - attributes = check_valid_attributes node, ValidEnumDefAttributes, "enum" - attributes_doc = attributes_doc() + annotations = @annotations + + annotations_doc = annotations_doc() scope, name, enum_type = lookup_type_def(node) @@ -488,28 +535,30 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor enum_base_type = @program.int32 end - is_flags = Attribute.any?(attributes, "Flags") all_value = interpret_enum_value(NumberLiteral.new(0), enum_base_type) existed = !!enum_type - enum_type ||= begin - EnumType.new(@program, scope, name, enum_base_type, is_flags) - end + enum_type ||= EnumType.new(@program, scope, name, enum_base_type) enum_type.private = true if node.visibility.private? + process_annotations do |annotation_type, ann| + enum_type.flags = true if annotation_type == @program.flags_annotation + enum_type.add_annotation(annotation_type, ann) + end + node.resolved_type = enum_type attach_doc enum_type, node - enum_type.doc ||= attributes_doc - @attributes = nil + enum_type.doc ||= annotations_doc + @annotations = nil pushing_type(enum_type) do - counter = is_flags ? 1 : 0 + counter = enum_type.flags? ? 1 : 0 counter, all_value = visit_enum_members(node, node.members, counter, all_value, existed: existed, enum_type: enum_type, enum_base_type: enum_base_type, - is_flags: is_flags) + is_flags: enum_type.flags?) end if enum_type.types.empty? @@ -517,7 +566,7 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor end unless existed - if is_flags + if enum_type.flags? unless enum_type.types["None"]? none = NumberLiteral.new(0, enum_base_type.kind) none.type = enum_type @@ -752,9 +801,16 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor node.raise "can only declare fun at lib or global scope" end - call_convention = check_call_convention_attributes node - attributes = check_valid_attributes node, ValidFunDefAttributes, "fun" - node.doc ||= attributes_doc() + external = External.new(node.name, ([] of Arg), node.body, node.real_name).at(node) + + call_convention = nil + process_def_annotations(external) do |annotation_type, ann| + if annotation_type == @program.call_convention_annotation + call_convention = parse_call_convention(ann, call_convention) + end + end + + node.doc ||= annotations_doc() check_ditto node # Copy call convention from lib, if any @@ -764,12 +820,10 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor end # We fill the arguments and return type in TypeDeclarationVisitor - external = External.new(node.name, ([] of Arg), node.body, node.real_name).at(node) external.doc = node.doc external.call_convention = call_convention external.varargs = node.varargs? external.fun_def = node - process_def_attributes external, attributes node.external = external false @@ -844,24 +898,23 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor false end - def process_lib_attributes - attributes = @attributes - return {nil, nil} unless attributes - - @attributes = nil - link_attributes = [] of LinkAttribute + def process_lib_annotations + link_annotations = nil call_convention = nil - attributes.each do |attr| - case attr.name - when "Link" - link_attributes << LinkAttribute.from(attr) - when "CallConvention" - call_convention = parse_call_convention(attr, call_convention) - else - attr.raise "illegal attribute for lib, valid attributes are: Link, CallConvention" + + process_annotations do |annotation_type, ann| + case annotation_type + when @program.link_annotation + link_annotations ||= [] of LinkAnnotation + link_annotations << LinkAnnotation.from(ann) + when @program.call_convention_annotation + call_convention = parse_call_convention(ann, call_convention) end end - {link_attributes, call_convention} + + @annotations = nil + + {link_annotations, call_convention} end def include_in(current_type, node, kind) @@ -910,32 +963,16 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor type.metaclass end - def check_call_convention_attributes(node) - attributes = @attributes - return unless attributes - - call_convention = nil - - attributes.reject! do |attr| - next false unless attr.name == "CallConvention" - - call_convention = parse_call_convention(attr, call_convention) - true - end - - call_convention - end - - def parse_call_convention(attr, call_convention) + def parse_call_convention(ann, call_convention) if call_convention - attr.raise "call convention already specified" + ann.raise "call convention already specified" end - if attr.args.size != 1 - attr.wrong_number_of_arguments "attribute CallConvention", attr.args.size, 1 + if ann.args.size != 1 + ann.wrong_number_of_arguments "annotation CallConvention", ann.args.size, 1 end - call_convention_node = attr.args.first + call_convention_node = ann.args.first unless call_convention_node.is_a?(StringLiteral) call_convention_node.raise "argument to CallConvention must be a string" end @@ -967,59 +1004,27 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor @last_doc = node.doc end - def attributes_doc - @attributes.try(&.first?).try &.doc - end - - def process_def_attributes(node, attributes) - attributes.try &.each do |attribute| - case attribute.name - when "NoInline" then node.no_inline = true - when "AlwaysInline" then node.always_inline = true - when "Naked" then node.naked = true - when "ReturnsTwice" then node.returns_twice = true - when "Raises" then node.raises = true - end - end + def annotations_doc + @annotations.try(&.first?).try &.doc end - private def process_class_def_struct_attributes - extern = false - extern_union = false - packed = false - - @attributes.try &.each do |attr| - case attr.name - when "Extern" - unless attr.args.empty? - attr.raise "Extern attribute can't have positional arguments, only named arguments: 'union'" - end - - attr.named_args.try &.each do |named_arg| - case named_arg.name - when "union" - value = named_arg.value - if value.is_a?(BoolLiteral) - extern_union = value.value - else - value.raise "Extern 'union' attribute must be a boolean, not #{value.class_desc}" - end - else - named_arg.raise "unknown Extern named argument, valid arguments are: 'union'" - end - end - - extern = true - when "Packed" - packed = true + def process_def_annotations(node) + process_annotations do |annotation_type, ann| + case annotation_type + when @program.no_inline_annotation + node.no_inline = true + when @program.always_inline_annotation + node.always_inline = true + when @program.naked_annotation + node.naked = true + when @program.returns_twice_annotation + node.returns_twice = true + when @program.raises_annotation + node.raises = true else - attr.raise "illegal attribute for struct declaration, valid attributes are: Packed, Extern" + yield annotation_type, ann end end - - @attributes = nil - - {extern, extern_union, packed} end def lookup_type_def(node : ASTNode) diff --git a/src/compiler/crystal/semantic/type_declaration_processor.cr b/src/compiler/crystal/semantic/type_declaration_processor.cr index 425eb048d6de..8c4a679e902c 100644 --- a/src/compiler/crystal/semantic/type_declaration_processor.cr +++ b/src/compiler/crystal/semantic/type_declaration_processor.cr @@ -13,7 +13,8 @@ struct Crystal::TypeDeclarationProcessor record TypeDeclarationWithLocation, type : Type, location : Location, - uninitialized : Bool + uninitialized : Bool, + annotations : Array({AnnotationType, Annotation})? # This captures an initialize info: it's related Def, # and which instance variables are assigned. Useful @@ -55,11 +56,17 @@ struct Crystal::TypeDeclarationProcessor class InstanceVarTypeInfo property type : Type property outside_def + property annotations : Array({AnnotationType, Annotation})? getter location def initialize(@location : Location, @type : Type) @outside_def = false end + + def add_annotations(anns : Array({AnnotationType, Annotation})?) + annotations = @annotations ||= [] of {AnnotationType, Annotation} + annotations.concat(anns) + end end record NilableInstanceVar, @@ -156,7 +163,7 @@ struct Crystal::TypeDeclarationProcessor {node, self} end - private def declare_meta_type_var(vars, owner, name, type : Type, location : Location? = nil, instance_var = false, freeze_type = true) + private def declare_meta_type_var(vars, owner, name, type : Type, location : Location? = nil, instance_var = false, freeze_type = true, annotations = nil) if instance_var && location && !owner.allows_instance_vars? raise_cant_declare_instance_var(owner, location) end @@ -177,7 +184,13 @@ struct Crystal::TypeDeclarationProcessor var.bind_to(var) var.freeze_type = type if freeze_type var.location = location + + annotations.try &.each do |annotation_type, ann| + var.add_annotation(annotation_type, ann) + end + vars[name] = var + var end @@ -192,7 +205,7 @@ struct Crystal::TypeDeclarationProcessor raise_cant_declare_instance_var(owner, info.location) end - var = declare_meta_type_var(vars, owner, name, info.type.as(Type), info.location, freeze_type: freeze_type) + var = declare_meta_type_var(vars, owner, name, info.type.as(Type), info.location, freeze_type: freeze_type, annotations: info.annotations) var.location = info.location # Check if var is uninitialized @@ -260,7 +273,7 @@ struct Crystal::TypeDeclarationProcessor if owner.is_a?(GenericType) owner.generic_types.each_value do |generic_type| new_type = type_decl.type.replace_type_parameters(generic_type) - new_type_decl = TypeDeclarationWithLocation.new(new_type, type_decl.location, type_decl.uninitialized) + new_type_decl = TypeDeclarationWithLocation.new(new_type, type_decl.location, type_decl.uninitialized, type_decl.annotations) declare_meta_type_var(generic_type.instance_vars, generic_type, name, new_type_decl, instance_var: true, check_nilable: false) end end @@ -346,7 +359,7 @@ struct Crystal::TypeDeclarationProcessor raise_nil_instance_var owner, name, type_info.location end - declare_meta_type_var(owner.instance_vars, owner, name, type, type_info.location, instance_var: true) + declare_meta_type_var(owner.instance_vars, owner, name, type, type_info.location, instance_var: true, annotations: type_info.annotations) when NonGenericModuleType type = type_info.type if nilable_instance_var?(owner, name) @@ -358,7 +371,7 @@ struct Crystal::TypeDeclarationProcessor return end - declare_meta_type_var(owner.instance_vars, owner, name, type, type_info.location, instance_var: true) + declare_meta_type_var(owner.instance_vars, owner, name, type, type_info.location, instance_var: true, annotations: type_info.annotations) remove_error owner, name owner.raw_including_types.try &.each do |including_type| process_owner_guessed_instance_var_declaration(including_type, name, type_info) @@ -375,11 +388,11 @@ struct Crystal::TypeDeclarationProcessor raise_nil_instance_var owner, name, type_info.location end - declare_meta_type_var(owner.instance_vars, owner, name, type, type_info.location, instance_var: true) + declare_meta_type_var(owner.instance_vars, owner, name, type, type_info.location, instance_var: true, annotations: type_info.annotations) owner.generic_types.each_value do |generic_type| new_type = type.replace_type_parameters(generic_type) - declare_meta_type_var(generic_type.instance_vars, generic_type, name, new_type, type_info.location, instance_var: true) + declare_meta_type_var(generic_type.instance_vars, generic_type, name, new_type, type_info.location, instance_var: true, annotations: type_info.annotations) end remove_error owner, name @@ -389,7 +402,7 @@ struct Crystal::TypeDeclarationProcessor type = Type.merge!([type, @program.nil]) end - declare_meta_type_var(owner.instance_vars, owner, name, type, type_info.location, instance_var: true) + declare_meta_type_var(owner.instance_vars, owner, name, type, type_info.location, instance_var: true, annotations: type_info.annotations) remove_error owner, name owner.raw_including_types.try &.each do |including_type| process_owner_guessed_instance_var_declaration(including_type, name, type_info) diff --git a/src/compiler/crystal/semantic/type_declaration_visitor.cr b/src/compiler/crystal/semantic/type_declaration_visitor.cr index c5eb609757be..e24869c8368e 100644 --- a/src/compiler/crystal/semantic/type_declaration_visitor.cr +++ b/src/compiler/crystal/semantic/type_declaration_visitor.cr @@ -22,8 +22,6 @@ require "./type_guess_visitor" # declared all types so now we can search them and always find # them, not needing any kind of forward referencing. class Crystal::TypeDeclarationVisitor < Crystal::SemanticVisitor - ValidExternalVarAttributes = %w(ThreadLocal) - alias TypeDeclarationWithLocation = TypeDeclarationProcessor::TypeDeclarationWithLocation getter class_vars @@ -76,7 +74,7 @@ class Crystal::TypeDeclarationVisitor < Crystal::SemanticVisitor def visit(node : LibDef) pushing_type(node.resolved_type) do @in_lib = true - @attributes = nil + @annotations = nil node.body.accept self @in_lib = false end @@ -95,11 +93,13 @@ class Crystal::TypeDeclarationVisitor < Crystal::SemanticVisitor end def visit(node : ExternalVar) - attributes = check_valid_attributes node, ValidExternalVarAttributes, "external var" + thread_local = false + process_annotations do |ann| + thread_local = true if ann == @program.thread_local_annotation + end var_type = lookup_type(node.type_spec) var_type = check_allowed_in_lib node.type_spec, var_type - thread_local = Attribute.any?(attributes, "ThreadLocal") type = current_type.as(LibType) type.add_var node.name, var_type, (node.real_name || node.name), thread_local @@ -227,10 +227,17 @@ class Crystal::TypeDeclarationVisitor < Crystal::SemanticVisitor end def declare_instance_var(owner, node, var) + annotations = nil + process_annotations do |annotation_type, ann| + annotations ||= [] of {AnnotationType, Annotation} + annotations << {annotation_type, ann} + end + var_type = lookup_type(node.declared_type) var_type = check_declare_var_type(node, var_type, "an instance variable") owner_vars = @instance_vars[owner] ||= {} of String => TypeDeclarationWithLocation - type_decl = TypeDeclarationWithLocation.new(var_type.virtual_type, node.location.not_nil!, false) + type_decl = TypeDeclarationWithLocation.new(var_type.virtual_type, node.location.not_nil!, + false, annotations) owner_vars[var.name] = type_decl end @@ -239,7 +246,7 @@ class Crystal::TypeDeclarationVisitor < Crystal::SemanticVisitor var_type = lookup_type(node.declared_type) var_type = check_declare_var_type(node, var_type, "a class variable") owner_vars = @class_vars[owner] ||= {} of String => TypeDeclarationWithLocation - owner_vars[var.name] = TypeDeclarationWithLocation.new(var_type.virtual_type, node.location.not_nil!, uninitialized) + owner_vars[var.name] = TypeDeclarationWithLocation.new(var_type.virtual_type, node.location.not_nil!, uninitialized, nil) end def visit(node : UninitializedVar) diff --git a/src/compiler/crystal/semantic/type_guess_visitor.cr b/src/compiler/crystal/semantic/type_guess_visitor.cr index 0dc707d9931c..77fad6f49e9d 100644 --- a/src/compiler/crystal/semantic/type_guess_visitor.cr +++ b/src/compiler/crystal/semantic/type_guess_visitor.cr @@ -397,14 +397,22 @@ module Crystal end def add_instance_var_type_info(vars, name, type : Type, node) + annotations = nil + process_annotations do |annotation_type, ann| + annotations ||= [] of {AnnotationType, Annotation} + annotations << {annotation_type, ann} + end + info = vars[name]? unless info info = InstanceVarTypeInfo.new(node.location.not_nil!, type) info.outside_def = true if @outside_def + info.add_annotations(annotations) if annotations vars[name] = info else info.type = Type.merge!([info.type, type]) info.outside_def = true if @outside_def + info.add_annotations(annotations) if annotations vars[name] = info end end diff --git a/src/compiler/crystal/syntax/ast.cr b/src/compiler/crystal/syntax/ast.cr index a004894cb78b..62eae67b9fcd 100644 --- a/src/compiler/crystal/syntax/ast.cr +++ b/src/compiler/crystal/syntax/ast.cr @@ -1295,6 +1295,29 @@ module Crystal def_equals_and_hash @name, @body, @type_vars, @splat_index end + # Annotation definition: + # + # 'annotation' name + # 'end' + # + class AnnotationDef < ASTNode + property name : Path + property doc : String? + property name_column_number : Int32 + + def initialize(@name, @name_column_number = 0) + end + + def accept_children(visitor) + end + + def clone_without_location + AnnotationDef.new(@name, @name_column_number) + end + + def_equals_and_hash @name + end + # While expression. # # 'while' cond @@ -1895,29 +1918,26 @@ module Crystal def_equals_and_hash expressions end - class Attribute < ASTNode - property name : String + class Annotation < ASTNode + property path : Path property args : Array(ASTNode) property named_args : Array(NamedArgument)? property doc : String? - def initialize(@name, @args = [] of ASTNode, @named_args = nil) + def initialize(@path, @args = [] of ASTNode, @named_args = nil) end def accept_children(visitor) + @path.accept visitor @args.each &.accept visitor @named_args.try &.each &.accept visitor end def clone_without_location - Attribute.new(name, @args.clone, @named_args.clone) - end - - def self.any?(attributes, name) - !!(attributes.try &.any? { |attr| attr.name == name }) + Annotation.new(@path.clone, @args.clone, @named_args.clone) end - def_equals_and_hash name, args, named_args + def_equals_and_hash path, args, named_args end # A macro expression, diff --git a/src/compiler/crystal/syntax/lexer.cr b/src/compiler/crystal/syntax/lexer.cr index 34cde94a9c70..21709c89d66b 100644 --- a/src/compiler/crystal/syntax/lexer.cr +++ b/src/compiler/crystal/syntax/lexer.cr @@ -734,6 +734,10 @@ module Crystal else return check_ident_or_keyword(:as, start) end + when 'n' + if next_char == 'n' && next_char == 'o' && next_char == 't' && next_char == 'a' && next_char == 't' && next_char == 'i' && next_char == 'o' && next_char == 'n' + return check_ident_or_keyword(:annotation, start) + end end scan_ident(start) when 'b' diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index 6ea48321f7e6..f861eca1306a 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -864,7 +864,7 @@ module Crystal when :"->" parse_fun_literal when :"@[" - parse_attribute + parse_annotation when :NUMBER @wants_regex = false node_and_next_token NumberLiteral.new(@token.value.to_s, @token.number_kind) @@ -1058,6 +1058,12 @@ module Crystal check_type_declaration { parse_visibility_modifier Visibility::Protected } when :asm check_type_declaration { parse_asm } + when :annotation + check_type_declaration do + check_not_inside_def("can't define annotation inside def") do + parse_annotation_def + end + end else set_visibility parse_var_or_call end @@ -1179,12 +1185,12 @@ module Crystal @def_nest > 0 end - def parse_attribute + def parse_annotation doc = @token.doc next_token_skip_space - name = check_const - next_token_skip_space + name = parse_ident(allow_type_vars: false, parse_nilable: false).as(Path) + skip_space args = [] of ASTNode named_args = nil @@ -1213,9 +1219,9 @@ module Crystal @wants_regex = false next_token_skip_space - attr = Attribute.new(name, args, named_args) - attr.doc = doc - attr + ann = Annotation.new(name, args, named_args) + ann.doc = doc + ann end def parse_begin @@ -1597,6 +1603,33 @@ module Crystal module_def end + def parse_annotation_def + @type_nest += 1 + + location = @token.location + doc = @token.doc + + next_token_skip_space_or_newline + + name_column_number = @token.column_number + name = parse_ident(allow_type_vars: false, parse_nilable: false).as(Path) + + unless name.names.last.ends_with?("Annotation") + raise "annotation name must end with 'Annotation'", name.location.not_nil! + end + + skip_statement_end + + end_location = token_end_location + check_ident :end + next_token_skip_space + + annotation_def = AnnotationDef.new name, name_column_number + annotation_def.doc = doc + annotation_def.end_location = end_location + annotation_def + end + def parse_parenthesized_expression location = @token.location slash_is_regex! @@ -5027,7 +5060,7 @@ module Crystal def parse_lib_body_exp_without_location case @token.type when :"@[" - parse_attribute + parse_annotation when :IDENT case @token.value when :alias diff --git a/src/compiler/crystal/syntax/to_s.cr b/src/compiler/crystal/syntax/to_s.cr index 7512d0dcf490..9b0b1a49fdb3 100644 --- a/src/compiler/crystal/syntax/to_s.cr +++ b/src/compiler/crystal/syntax/to_s.cr @@ -314,6 +314,16 @@ module Crystal false end + def visit(node : AnnotationDef) + @str << keyword("annotation") + @str << ' ' + node.name.accept self + newline + append_indent + @str << keyword("end") + false + end + def visit(node : Call) visit_call node end @@ -1383,9 +1393,9 @@ module Crystal false end - def visit(node : Attribute) + def visit(node : Annotation) @str << "@[" - @str << node.name + @str << node.path if !node.args.empty? || node.named_args @str << '(' printed_arg = false diff --git a/src/compiler/crystal/syntax/transformer.cr b/src/compiler/crystal/syntax/transformer.cr index 5ff1c5940c29..feda5ced7007 100644 --- a/src/compiler/crystal/syntax/transformer.cr +++ b/src/compiler/crystal/syntax/transformer.cr @@ -244,6 +244,10 @@ module Crystal node end + def transform(node : AnnotationDef) + node + end + def transform(node : While) node.cond = node.cond.transform(self) node.body = node.body.transform(self) @@ -544,7 +548,7 @@ module Crystal node end - def transform(node : Attribute) + def transform(node : Annotation) node end diff --git a/src/compiler/crystal/tools/doc/type.cr b/src/compiler/crystal/tools/doc/type.cr index f5e68be7da0e..e0877be69c33 100644 --- a/src/compiler/crystal/tools/doc/type.cr +++ b/src/compiler/crystal/tools/doc/type.cr @@ -24,6 +24,8 @@ class Crystal::Doc::Type :enum when NoReturnType, VoidType :struct + when AnnotationType + :annotation else raise "Unhandled type in `kind`: #{@type}" end diff --git a/src/compiler/crystal/tools/formatter.cr b/src/compiler/crystal/tools/formatter.cr index 7c09adfc8abf..a53c802ba61b 100644 --- a/src/compiler/crystal/tools/formatter.cr +++ b/src/compiler/crystal/tools/formatter.cr @@ -359,7 +359,7 @@ module Crystal end def needs_two_lines?(node, next_node) - return false if node.is_a?(Attribute) || node.is_a?(MacroIf) + return false if node.is_a?(Annotation) || node.is_a?(MacroIf) return false if abstract_def?(node) && abstract_def?(next_node) needs_two_lines?(node) || needs_two_lines?(next_node) @@ -3086,6 +3086,32 @@ module Crystal false end + def visit(node : AnnotationDef) + write_keyword :annotation, " " + + accept node.name + + skip_space(@indent + 2) + + if @token.type == :";" + skip_semicolon_or_space_or_newline + check_end + write "; end" + next_token + return false + else + skip_space_or_newline + check_end + write_line + write_indent + write "end" + next_token + return false + end + + false + end + def visit(node : ClassDef) write_keyword :abstract, " " if node.abstract? write_keyword (node.struct? ? :struct : :class), " " @@ -3460,7 +3486,7 @@ module Crystal false end - def visit(node : Attribute) + def visit(node : Annotation) write_token :"@[" skip_space_or_newline diff --git a/src/compiler/crystal/types.cr b/src/compiler/crystal/types.cr index 87768ebafdc4..a953b531e60f 100644 --- a/src/compiler/crystal/types.cr +++ b/src/compiler/crystal/types.cr @@ -162,6 +162,10 @@ module Crystal self.is_a?(NilType) end + def nilable? + self.is_a?(NilType) || (self.is_a?(UnionType) && self.union_types.any?(&.nil_type?)) + end + def bool_type? self.is_a?(BoolType) end @@ -282,10 +286,11 @@ module Crystal raise "BUG: #{self} doesn't implement add_instance_var_initializer" end - def declare_instance_var(name, type : Type) + def declare_instance_var(name, type : Type, annotations = nil) var = MetaTypeVar.new(name) var.owner = self var.type = type + var.annotations = annotations var.bind_to var var.freeze_type = type instance_vars[name] = var @@ -597,6 +602,21 @@ module Crystal end end + # Adds an annotation with the given type and value + def add_annotation(annotation_type : AnnotationType, value : Annotation) + annotations = @annotations ||= {} of AnnotationType => Annotation + annotations[annotation_type] = value + end + + # Returns the annotation with the given type, if any, or nil otherwise + def annotation(annotation_type) : Annotation? + @annotations.try &.[annotation_type] + end + + def get_instance_var_initializer(name) + nil + end + def inspect(io) to_s(io) end @@ -941,6 +961,20 @@ module Crystal @instance_vars_initializers.try(&.any? { |init| init.name == name }) || ancestors.any?(&.has_instance_var_initializer?(name)) end + + def get_instance_var_initializer(name) + match = @instance_vars_initializers.try &.find do |init| + init.name == name + end + return match if match + + ancestors.each do |ancestor| + match = ancestor.get_instance_var_initializer(name) + return match if match + end + + nil + end end # A type that can have instance variables. @@ -1294,7 +1328,7 @@ module Crystal else instance_var_type = ivar_type.replace_type_parameters(instance) end - instance.declare_instance_var(name, instance_var_type) + instance.declare_instance_var(name, instance_var_type, ivar.annotations) end run_instance_vars_initializers self, self, instance @@ -2251,17 +2285,13 @@ module Crystal # A lib type, like `lib LibC`. class LibType < ModuleType - getter link_attributes : Array(LinkAttribute)? + getter link_annotations : Array(LinkAnnotation)? property? used = false property call_convention : LLVM::CallConvention? - def add_link_attributes(link_attributes) - if link_attributes - my_link_attributes = @link_attributes ||= [] of LinkAttribute - link_attributes.each do |attr| - my_link_attributes << attr unless my_link_attributes.includes?(attr) - end - end + def add_link_annotation(link_annotation : LinkAnnotation) + link_annotations = @link_annotations ||= [] of LinkAnnotation + link_annotations << link_annotation unless link_annotations.includes?(link_annotation) end def metaclass @@ -2421,13 +2451,11 @@ module Crystal include ClassVarContainer getter base_type : IntegerType - getter? flags : Bool + property? flags = false - def initialize(program, namespace, name, @base_type, flags) + def initialize(program, namespace, name, @base_type) super(program, namespace, name) - @flags = !!flags - add_def Def.new("value", [] of Arg, Primitive.new("enum_value", @base_type)) metaclass.as(ModuleType).add_def Def.new("new", [Arg.new("value", type: @base_type)], Primitive.new("enum_new", self)) end @@ -2456,6 +2484,12 @@ module Crystal end end + class AnnotationType < NamedType + def type_desc + "annotation" + end + end + # A metaclass type, that results from invoking `.class` on a type. # # For example `String:Class` is the metaclass of `String`, and it's diff --git a/src/enum.cr b/src/enum.cr index b142fc4e3df6..141d4f06dcff 100644 --- a/src/enum.cr +++ b/src/enum.cr @@ -26,7 +26,7 @@ # # ### Flags enum # -# An enum can be marked with the `@[Flags]` attribute. This changes the default values: +# An enum can be marked with the `FlagsAnnotation` annotation. This changes the default values: # # ``` # @[Flags] From 96a658d31ce18e860e205302700f2763c7d33e2e Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Sat, 5 May 2018 08:29:52 -0300 Subject: [PATCH 02/16] Don't require annotations to end with "Annotation" --- spec/compiler/parser/parser_spec.cr | 7 ++- spec/compiler/semantic/annotation_spec.cr | 54 +++++++++---------- src/compiler/crystal/program.cr | 32 +++++------ src/compiler/crystal/semantic/main_visitor.cr | 12 ++--- .../crystal/semantic/semantic_visitor.cr | 23 +++++--- .../crystal/semantic/top_level_visitor.cr | 28 +++++----- .../semantic/type_declaration_visitor.cr | 2 +- src/compiler/crystal/syntax/parser.cr | 4 -- 8 files changed, 83 insertions(+), 79 deletions(-) diff --git a/spec/compiler/parser/parser_spec.cr b/spec/compiler/parser/parser_spec.cr index c45260d0c6d9..a6567d04158c 100644 --- a/spec/compiler/parser/parser_spec.cr +++ b/spec/compiler/parser/parser_spec.cr @@ -1680,10 +1680,9 @@ describe "Parser" do assert_syntax_error "<<-HEREDOC", "Unexpected EOF on heredoc identifier" assert_syntax_error "<<-HEREDOC\n", "Unterminated heredoc" - it_parses "annotation FooAnnotation; end", AnnotationDef.new("FooAnnotation".path) - it_parses "annotation FooAnnotation\n\nend", AnnotationDef.new("FooAnnotation".path) - it_parses "annotation Foo::BarAnnotation\n\nend", AnnotationDef.new(Path.new(["Foo", "BarAnnotation"])) - assert_syntax_error "annotation Foo", "annotation name must end with 'Annotation'" + it_parses "annotation Foo; end", AnnotationDef.new("Foo".path) + it_parses "annotation Foo\n\nend", AnnotationDef.new("Foo".path) + it_parses "annotation Foo::Bar\n\nend", AnnotationDef.new(Path.new(["Foo", "Bar"])) it "gets corrects of ~" do node = Parser.parse("\n ~1") diff --git a/spec/compiler/semantic/annotation_spec.cr b/spec/compiler/semantic/annotation_spec.cr index 0d4f912f48e3..c8895603e8c0 100644 --- a/spec/compiler/semantic/annotation_spec.cr +++ b/spec/compiler/semantic/annotation_spec.cr @@ -3,24 +3,24 @@ require "../../spec_helper" describe "Semantic: annotation" do it "declares annotation" do result = semantic(%( - annotation FooAnnotation + annotation Foo end )) - type = result.program.types["FooAnnotation"] + type = result.program.types["Foo"] type.should be_a(AnnotationType) - type.name.should eq("FooAnnotation") + type.name.should eq("Foo") end it "can't find annotation in module" do assert_type(%( - annotation FooAnnotation + annotation Foo end module Moo end - {% if Moo.annotation(FooAnnotation) %} + {% if Moo.annotation(Foo) %} 1 {% else %} 'a' @@ -30,14 +30,14 @@ describe "Semantic: annotation" do it "finds annotation in module" do assert_type(%( - annotation FooAnnotation + annotation Foo end @[Foo] module Moo end - {% if Moo.annotation(FooAnnotation) %} + {% if Moo.annotation(Foo) %} 1 {% else %} 'a' @@ -47,14 +47,14 @@ describe "Semantic: annotation" do it "uses annotation value, positional" do assert_type(%( - annotation FooAnnotation + annotation Foo end @[Foo(1)] module Moo end - {% if Moo.annotation(FooAnnotation)[0] == 1 %} + {% if Moo.annotation(Foo)[0] == 1 %} 1 {% else %} 'a' @@ -64,14 +64,14 @@ describe "Semantic: annotation" do it "uses annotation value, keyword" do assert_type(%( - annotation FooAnnotation + annotation Foo end @[Foo(x: 1)] module Moo end - {% if Moo.annotation(FooAnnotation)[:x] == 1 %} + {% if Moo.annotation(Foo)[:x] == 1 %} 1 {% else %} 'a' @@ -81,14 +81,14 @@ describe "Semantic: annotation" do it "finds annotation in class" do assert_type(%( - annotation FooAnnotation + annotation Foo end @[Foo] class Moo end - {% if Moo.annotation(FooAnnotation) %} + {% if Moo.annotation(Foo) %} 1 {% else %} 'a' @@ -98,14 +98,14 @@ describe "Semantic: annotation" do it "finds annotation in struct" do assert_type(%( - annotation FooAnnotation + annotation Foo end @[Foo] struct Moo end - {% if Moo.annotation(FooAnnotation) %} + {% if Moo.annotation(Foo) %} 1 {% else %} 'a' @@ -115,7 +115,7 @@ describe "Semantic: annotation" do it "finds annotation in enum" do assert_type(%( - annotation FooAnnotation + annotation Foo end @[Foo] @@ -123,7 +123,7 @@ describe "Semantic: annotation" do A = 1 end - {% if Moo.annotation(FooAnnotation) %} + {% if Moo.annotation(Foo) %} 1 {% else %} 'a' @@ -133,7 +133,7 @@ describe "Semantic: annotation" do it "finds annotation in lib" do assert_type(%( - annotation FooAnnotation + annotation Foo end @[Foo] @@ -141,7 +141,7 @@ describe "Semantic: annotation" do A = 1 end - {% if Moo.annotation(FooAnnotation) %} + {% if Moo.annotation(Foo) %} 1 {% else %} 'a' @@ -151,14 +151,14 @@ describe "Semantic: annotation" do it "can't find annotation in instance var" do assert_type(%( - annotation FooAnnotation + annotation Foo end class Moo @x : Int32 = 1 def foo - {% if @type.instance_vars.first.annotation(FooAnnotation) %} + {% if @type.instance_vars.first.annotation(Foo) %} 1 {% else %} 'a' @@ -172,7 +172,7 @@ describe "Semantic: annotation" do it "finds annotation in instance var (declaration)" do assert_type(%( - annotation FooAnnotation + annotation Foo end class Moo @@ -180,7 +180,7 @@ describe "Semantic: annotation" do @x : Int32 = 1 def foo - {% if @type.instance_vars.first.annotation(FooAnnotation) %} + {% if @type.instance_vars.first.annotation(Foo) %} 1 {% else %} 'a' @@ -194,7 +194,7 @@ describe "Semantic: annotation" do it "finds annotation in instance var (assignment)" do assert_type(%( - annotation FooAnnotation + annotation Foo end class Moo @@ -202,7 +202,7 @@ describe "Semantic: annotation" do @x = 1 def foo - {% if @type.instance_vars.first.annotation(FooAnnotation) %} + {% if @type.instance_vars.first.annotation(Foo) %} 1 {% else %} 'a' @@ -216,7 +216,7 @@ describe "Semantic: annotation" do it "finds annotation in instance var (declaration, generic)" do assert_type(%( - annotation FooAnnotation + annotation Foo end class Moo(T) @@ -227,7 +227,7 @@ describe "Semantic: annotation" do end def foo - {% if @type.instance_vars.first.annotation(FooAnnotation) %} + {% if @type.instance_vars.first.annotation(Foo) %} 1 {% else %} 'a' diff --git a/src/compiler/crystal/program.cr b/src/compiler/crystal/program.cr index ec9750cc472a..6b18277955f1 100644 --- a/src/compiler/crystal/program.cr +++ b/src/compiler/crystal/program.cr @@ -212,18 +212,18 @@ module Crystal gc.metaclass.as(ModuleType).add_def Def.new("add_finalizer", [Arg.new("object")], Nop.new) # Built-in annotations - types["AlwaysInlineAnnotation"] = @always_inline_annotation = AnnotationType.new self, self, "AlwaysInlineAnnotation" - types["CallConventionAnnotation"] = @call_convention_annotation = AnnotationType.new self, self, "CallConventionAnnotation" - types["ExternAnnotation"] = @extern_annotation = AnnotationType.new self, self, "ExternAnnotation" - types["FlagsAnnotation"] = @flags_annotation = AnnotationType.new self, self, "FlagsAnnotation" - types["LinkAnnotation"] = @link_annotation = AnnotationType.new self, self, "LinkAnnotation" - types["NakedAnnotation"] = @naked_annotation = AnnotationType.new self, self, "NakedAnnotation" - types["NoInlineAnnotation"] = @no_inline_annotation = AnnotationType.new self, self, "NoInlineAnnotation" - types["PackedAnnotation"] = @packed_annotation = AnnotationType.new self, self, "PackedAnnotation" - types["PrimitiveAnnotation"] = @primitive_annotation = AnnotationType.new self, self, "PrimitiveAnnotation" - types["RaisesAnnotation"] = @raises_annotation = AnnotationType.new self, self, "RaisesAnnotation" - types["ReturnsTwiceAnnotation"] = @returns_twice_annotation = AnnotationType.new self, self, "ReturnsTwiceAnnotation" - types["ThreadLocalAnnotation"] = @thread_local_annotation = AnnotationType.new self, self, "ThreadLocalAnnotation" + types["AlwaysInline"] = @always_inline = AnnotationType.new self, self, "AlwaysInline" + types["CallConvention"] = @call_convention = AnnotationType.new self, self, "CallConvention" + types["Extern"] = @extern = AnnotationType.new self, self, "Extern" + types["Flags"] = @flags_annotation = AnnotationType.new self, self, "Flags" + types["Link"] = @link = AnnotationType.new self, self, "Link" + types["Naked"] = @naked = AnnotationType.new self, self, "Naked" + types["NoInline"] = @no_inline = AnnotationType.new self, self, "NoInline" + types["Packed"] = @packed = AnnotationType.new self, self, "Packed" + types["Primitive"] = @primitive = AnnotationType.new self, self, "Primitive" + types["Raises"] = @raises = AnnotationType.new self, self, "Raises" + types["ReturnsTwice"] = @returns_twice = AnnotationType.new self, self, "ReturnsTwice" + types["ThreadLocal"] = @thread_local = AnnotationType.new self, self, "ThreadLocal" define_crystal_constants end @@ -445,10 +445,10 @@ module Crystal {% for name in %w(object no_return value number reference void nil bool char int int8 int16 int32 int64 int128 uint8 uint16 uint32 uint64 uint128 float float32 float64 string symbol pointer array static_array exception tuple named_tuple proc union enum range regex crystal - packed_annotation thread_local_annotation no_inline_annotation - always_inline_annotation naked_annotation returns_twice_annotation - raises_annotation primitive_annotation call_convention_annotation - flags_annotation link_annotation extern_annotation) %} + packed thread_local no_inline + always_inline naked returns_twice + raises primitive call_convention + flags_annotation link extern) %} def {{name.id}} @{{name.id}}.not_nil! end diff --git a/src/compiler/crystal/semantic/main_visitor.cr b/src/compiler/crystal/semantic/main_visitor.cr index a2180db9f5fe..4468402292fa 100644 --- a/src/compiler/crystal/semantic/main_visitor.cr +++ b/src/compiler/crystal/semantic/main_visitor.cr @@ -417,7 +417,7 @@ module Crystal thread_local = false process_annotations do |ann| - thread_local = true if ann == @program.thread_local_annotation + thread_local = true if ann == @program.thread_local end class_var = lookup_class_var(var) @@ -430,7 +430,7 @@ module Crystal thread_local = false process_annotations do |ann| - thread_local = true if ann == @program.thread_local_annotation + thread_local = true if ann == @program.thread_local end if thread_local @@ -519,7 +519,7 @@ module Crystal when ClassVar thread_local = false process_annotations do |ann| - thread_local = true if ann == @program.thread_local_annotation + thread_local = true if ann == @program.thread_local end class_var = visit_class_var var @@ -689,7 +689,7 @@ module Crystal def visit(node : ClassVar) thread_local = false process_annotations do |ann| - thread_local = true if ann == @program.thread_local_annotation + thread_local = true if ann == @program.thread_local end var = visit_class_var node @@ -886,7 +886,7 @@ module Crystal def type_assign(target : Global, value, node) thread_local = false process_annotations do |ann| - thread_local = true if ann == @program.thread_local_annotation + thread_local = true if ann == @program.thread_local end value.accept self @@ -912,7 +912,7 @@ module Crystal def type_assign(target : ClassVar, value, node) thread_local = false process_annotations do |ann| - thread_local = true if ann == @program.thread_local_annotation + thread_local = true if ann == @program.thread_local end # Outside a def is already handled by ClassVarsInitializerVisitor diff --git a/src/compiler/crystal/semantic/semantic_visitor.cr b/src/compiler/crystal/semantic/semantic_visitor.cr index 56a2472099ee..037e616f57ed 100644 --- a/src/compiler/crystal/semantic/semantic_visitor.cr +++ b/src/compiler/crystal/semantic/semantic_visitor.cr @@ -419,18 +419,27 @@ abstract class Crystal::SemanticVisitor < Crystal::Visitor end def lookup_annotation(ann) - # For `@[Foo]` we actually search an annotation named `FooAnnotation` - path = ann.path.clone - path.names[-1] = "#{path.names.last}Annotation" - - type = lookup_type(path) + # Since there's `Int::Primitive`, and now we'll have + # `::Primitive`, but there's no way to specify ::Primitive + # just yet in annotations, we temporarily hardcode + # that `Primitive` inside annotations means the top + # level primitive. + # We also have the same problem with File::Flags, which + # is an enum marked with Flags annotation. + if ann.path.single?("Primitive") + type = @program.primitive + elsif ann.path.single?("Flags") + type = @program.flags_annotation + else + type = lookup_type(ann.path) + end unless type - ann.raise "undefined annotation #{path}" + ann.raise "undefined annotation #{ann.path}" end unless type.is_a?(AnnotationType) - ann.raise "#{path} is not an annotation, it's a #{type.type_desc}" + ann.raise "#{ann.path} is not an annotation, it's a #{type.type_desc}" end type diff --git a/src/compiler/crystal/semantic/top_level_visitor.cr b/src/compiler/crystal/semantic/top_level_visitor.cr index 8cbadb85631e..4816898579fd 100644 --- a/src/compiler/crystal/semantic/top_level_visitor.cr +++ b/src/compiler/crystal/semantic/top_level_visitor.cr @@ -158,7 +158,7 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor process_annotations(annotations) do |annotation_type, ann| if node.struct? && type.is_a?(NonGenericClassType) case annotation_type - when @program.extern_annotation + when @program.extern unless type.is_a?(NonGenericClassType) node.raise "can only use Extern annotation with non-generic structs" end @@ -182,7 +182,7 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor end type.extern = true - when @program.packed_annotation + when @program.packed type.packed = true end end @@ -314,7 +314,7 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor annotations = @annotations process_def_annotations(node) do |annotation_type, ann| - if annotation_type == @program.primitive_annotation + if annotation_type == @program.primitive process_primitive_annotation(node, ann) end end @@ -443,9 +443,9 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor process_annotations(annotations) do |annotation_type, ann| case annotation_type - when @program.link_annotation + when @program.link type.add_link_annotation(LinkAnnotation.from(ann)) - when @program.call_convention_annotation + when @program.call_convention type.call_convention = parse_call_convention(ann, type.call_convention) end type.add_annotation(annotation_type, ann) @@ -465,7 +465,7 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor unless node.union? process_annotations do |ann| - packed = true if ann == @program.packed_annotation + packed = true if ann == @program.packed end end @@ -805,7 +805,7 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor call_convention = nil process_def_annotations(external) do |annotation_type, ann| - if annotation_type == @program.call_convention_annotation + if annotation_type == @program.call_convention call_convention = parse_call_convention(ann, call_convention) end end @@ -904,10 +904,10 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor process_annotations do |annotation_type, ann| case annotation_type - when @program.link_annotation + when @program.link link_annotations ||= [] of LinkAnnotation link_annotations << LinkAnnotation.from(ann) - when @program.call_convention_annotation + when @program.call_convention call_convention = parse_call_convention(ann, call_convention) end end @@ -1011,15 +1011,15 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor def process_def_annotations(node) process_annotations do |annotation_type, ann| case annotation_type - when @program.no_inline_annotation + when @program.no_inline node.no_inline = true - when @program.always_inline_annotation + when @program.always_inline node.always_inline = true - when @program.naked_annotation + when @program.naked node.naked = true - when @program.returns_twice_annotation + when @program.returns_twice node.returns_twice = true - when @program.raises_annotation + when @program.raises node.raises = true else yield annotation_type, ann diff --git a/src/compiler/crystal/semantic/type_declaration_visitor.cr b/src/compiler/crystal/semantic/type_declaration_visitor.cr index e24869c8368e..fd4a9ce6f7a3 100644 --- a/src/compiler/crystal/semantic/type_declaration_visitor.cr +++ b/src/compiler/crystal/semantic/type_declaration_visitor.cr @@ -95,7 +95,7 @@ class Crystal::TypeDeclarationVisitor < Crystal::SemanticVisitor def visit(node : ExternalVar) thread_local = false process_annotations do |ann| - thread_local = true if ann == @program.thread_local_annotation + thread_local = true if ann == @program.thread_local end var_type = lookup_type(node.type_spec) diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index f861eca1306a..86f4f00acdb7 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -1614,10 +1614,6 @@ module Crystal name_column_number = @token.column_number name = parse_ident(allow_type_vars: false, parse_nilable: false).as(Path) - unless name.names.last.ends_with?("Annotation") - raise "annotation name must end with 'Annotation'", name.location.not_nil! - end - skip_statement_end end_location = token_end_location From 1d793af519042c563cd68c03e370a15d6c272802 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Sat, 5 May 2018 08:45:56 -0300 Subject: [PATCH 03/16] Add TODO about the hardcoded annotation rules --- src/compiler/crystal/semantic/semantic_visitor.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/crystal/semantic/semantic_visitor.cr b/src/compiler/crystal/semantic/semantic_visitor.cr index 037e616f57ed..24eb2c0f15b8 100644 --- a/src/compiler/crystal/semantic/semantic_visitor.cr +++ b/src/compiler/crystal/semantic/semantic_visitor.cr @@ -419,7 +419,7 @@ abstract class Crystal::SemanticVisitor < Crystal::Visitor end def lookup_annotation(ann) - # Since there's `Int::Primitive`, and now we'll have + # TODO: Since there's `Int::Primitive`, and now we'll have # `::Primitive`, but there's no way to specify ::Primitive # just yet in annotations, we temporarily hardcode # that `Primitive` inside annotations means the top From 8cd1ad03be0bc046f31826bf4a1509a7ed35b1b9 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Sat, 5 May 2018 09:22:52 -0300 Subject: [PATCH 04/16] Don't change enum docs for now --- src/enum.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/enum.cr b/src/enum.cr index 141d4f06dcff..b142fc4e3df6 100644 --- a/src/enum.cr +++ b/src/enum.cr @@ -26,7 +26,7 @@ # # ### Flags enum # -# An enum can be marked with the `FlagsAnnotation` annotation. This changes the default values: +# An enum can be marked with the `@[Flags]` attribute. This changes the default values: # # ``` # @[Flags] From b223a1783259c8aff20df13e61a592757b0d3cc9 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Tue, 8 May 2018 05:57:49 -0300 Subject: [PATCH 05/16] Use "annotation" suffix for Program variables that represent annotations --- src/compiler/crystal/codegen/link.cr | 8 ++--- src/compiler/crystal/program.cr | 30 +++++++++---------- src/compiler/crystal/semantic/main_visitor.cr | 12 ++++---- .../crystal/semantic/semantic_visitor.cr | 2 +- .../crystal/semantic/top_level_visitor.cr | 24 +++++++-------- .../semantic/type_declaration_visitor.cr | 2 +- 6 files changed, 39 insertions(+), 39 deletions(-) diff --git a/src/compiler/crystal/codegen/link.cr b/src/compiler/crystal/codegen/link.cr index 7ae4f2f3aa16..bb454a07ff63 100644 --- a/src/compiler/crystal/codegen/link.cr +++ b/src/compiler/crystal/codegen/link.cr @@ -167,15 +167,15 @@ module Crystal nil end - private def add_link_annotations(types, attrs) + private def add_link_annotations(types, annotations) types.try &.each_value do |type| next if type.is_a?(AliasType) || type.is_a?(TypeDefType) - if type.is_a?(LibType) && type.used? && (link_attrs = type.link_annotations) - attrs.concat link_attrs + if type.is_a?(LibType) && type.used? && (links_annotations = type.link_annotations) + annotations.concat links_annotations end - add_link_annotations type.types?, attrs + add_link_annotations type.types?, annotations end end end diff --git a/src/compiler/crystal/program.cr b/src/compiler/crystal/program.cr index 6b18277955f1..afd0fb6556f2 100644 --- a/src/compiler/crystal/program.cr +++ b/src/compiler/crystal/program.cr @@ -212,18 +212,18 @@ module Crystal gc.metaclass.as(ModuleType).add_def Def.new("add_finalizer", [Arg.new("object")], Nop.new) # Built-in annotations - types["AlwaysInline"] = @always_inline = AnnotationType.new self, self, "AlwaysInline" - types["CallConvention"] = @call_convention = AnnotationType.new self, self, "CallConvention" - types["Extern"] = @extern = AnnotationType.new self, self, "Extern" + types["AlwaysInline"] = @always_inline_annotation = AnnotationType.new self, self, "AlwaysInline" + types["CallConvention"] = @call_convention_annotation = AnnotationType.new self, self, "CallConvention" + types["Extern"] = @extern_annotation = AnnotationType.new self, self, "Extern" types["Flags"] = @flags_annotation = AnnotationType.new self, self, "Flags" - types["Link"] = @link = AnnotationType.new self, self, "Link" - types["Naked"] = @naked = AnnotationType.new self, self, "Naked" - types["NoInline"] = @no_inline = AnnotationType.new self, self, "NoInline" - types["Packed"] = @packed = AnnotationType.new self, self, "Packed" - types["Primitive"] = @primitive = AnnotationType.new self, self, "Primitive" - types["Raises"] = @raises = AnnotationType.new self, self, "Raises" - types["ReturnsTwice"] = @returns_twice = AnnotationType.new self, self, "ReturnsTwice" - types["ThreadLocal"] = @thread_local = AnnotationType.new self, self, "ThreadLocal" + types["Link"] = @link_annotation = AnnotationType.new self, self, "Link" + types["Naked"] = @naked_annotation = AnnotationType.new self, self, "Naked" + types["NoInline"] = @no_inline_annotation = AnnotationType.new self, self, "NoInline" + types["Packed"] = @packed_annotation = AnnotationType.new self, self, "Packed" + types["Primitive"] = @primitive_annotation = AnnotationType.new self, self, "Primitive" + types["Raises"] = @raises_annotation = AnnotationType.new self, self, "Raises" + types["ReturnsTwice"] = @returns_twice_annotation = AnnotationType.new self, self, "ReturnsTwice" + types["ThreadLocal"] = @thread_local_annotation = AnnotationType.new self, self, "ThreadLocal" define_crystal_constants end @@ -445,10 +445,10 @@ module Crystal {% for name in %w(object no_return value number reference void nil bool char int int8 int16 int32 int64 int128 uint8 uint16 uint32 uint64 uint128 float float32 float64 string symbol pointer array static_array exception tuple named_tuple proc union enum range regex crystal - packed thread_local no_inline - always_inline naked returns_twice - raises primitive call_convention - flags_annotation link extern) %} + packed_annotation thread_local_annotation no_inline_annotation + always_inline_annotation naked_annotation returns_twice_annotation + raises_annotation primitive_annotation call_convention_annotation + flags_annotation link_annotation extern_annotation) %} def {{name.id}} @{{name.id}}.not_nil! end diff --git a/src/compiler/crystal/semantic/main_visitor.cr b/src/compiler/crystal/semantic/main_visitor.cr index 4468402292fa..a2180db9f5fe 100644 --- a/src/compiler/crystal/semantic/main_visitor.cr +++ b/src/compiler/crystal/semantic/main_visitor.cr @@ -417,7 +417,7 @@ module Crystal thread_local = false process_annotations do |ann| - thread_local = true if ann == @program.thread_local + thread_local = true if ann == @program.thread_local_annotation end class_var = lookup_class_var(var) @@ -430,7 +430,7 @@ module Crystal thread_local = false process_annotations do |ann| - thread_local = true if ann == @program.thread_local + thread_local = true if ann == @program.thread_local_annotation end if thread_local @@ -519,7 +519,7 @@ module Crystal when ClassVar thread_local = false process_annotations do |ann| - thread_local = true if ann == @program.thread_local + thread_local = true if ann == @program.thread_local_annotation end class_var = visit_class_var var @@ -689,7 +689,7 @@ module Crystal def visit(node : ClassVar) thread_local = false process_annotations do |ann| - thread_local = true if ann == @program.thread_local + thread_local = true if ann == @program.thread_local_annotation end var = visit_class_var node @@ -886,7 +886,7 @@ module Crystal def type_assign(target : Global, value, node) thread_local = false process_annotations do |ann| - thread_local = true if ann == @program.thread_local + thread_local = true if ann == @program.thread_local_annotation end value.accept self @@ -912,7 +912,7 @@ module Crystal def type_assign(target : ClassVar, value, node) thread_local = false process_annotations do |ann| - thread_local = true if ann == @program.thread_local + thread_local = true if ann == @program.thread_local_annotation end # Outside a def is already handled by ClassVarsInitializerVisitor diff --git a/src/compiler/crystal/semantic/semantic_visitor.cr b/src/compiler/crystal/semantic/semantic_visitor.cr index 24eb2c0f15b8..af1de363081c 100644 --- a/src/compiler/crystal/semantic/semantic_visitor.cr +++ b/src/compiler/crystal/semantic/semantic_visitor.cr @@ -427,7 +427,7 @@ abstract class Crystal::SemanticVisitor < Crystal::Visitor # We also have the same problem with File::Flags, which # is an enum marked with Flags annotation. if ann.path.single?("Primitive") - type = @program.primitive + type = @program.primitive_annotation elsif ann.path.single?("Flags") type = @program.flags_annotation else diff --git a/src/compiler/crystal/semantic/top_level_visitor.cr b/src/compiler/crystal/semantic/top_level_visitor.cr index 4816898579fd..92ec1d15999d 100644 --- a/src/compiler/crystal/semantic/top_level_visitor.cr +++ b/src/compiler/crystal/semantic/top_level_visitor.cr @@ -158,7 +158,7 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor process_annotations(annotations) do |annotation_type, ann| if node.struct? && type.is_a?(NonGenericClassType) case annotation_type - when @program.extern + when @program.extern_annotation unless type.is_a?(NonGenericClassType) node.raise "can only use Extern annotation with non-generic structs" end @@ -182,7 +182,7 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor end type.extern = true - when @program.packed + when @program.packed_annotation type.packed = true end end @@ -314,7 +314,7 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor annotations = @annotations process_def_annotations(node) do |annotation_type, ann| - if annotation_type == @program.primitive + if annotation_type == @program.primitive_annotation process_primitive_annotation(node, ann) end end @@ -443,9 +443,9 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor process_annotations(annotations) do |annotation_type, ann| case annotation_type - when @program.link + when @program.link_annotation type.add_link_annotation(LinkAnnotation.from(ann)) - when @program.call_convention + when @program.call_convention_annotation type.call_convention = parse_call_convention(ann, type.call_convention) end type.add_annotation(annotation_type, ann) @@ -465,7 +465,7 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor unless node.union? process_annotations do |ann| - packed = true if ann == @program.packed + packed = true if ann == @program.packed_annotation end end @@ -805,7 +805,7 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor call_convention = nil process_def_annotations(external) do |annotation_type, ann| - if annotation_type == @program.call_convention + if annotation_type == @program.call_convention_annotation call_convention = parse_call_convention(ann, call_convention) end end @@ -1011,15 +1011,15 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor def process_def_annotations(node) process_annotations do |annotation_type, ann| case annotation_type - when @program.no_inline + when @program.no_inline_annotation node.no_inline = true - when @program.always_inline + when @program.always_inline_annotation node.always_inline = true - when @program.naked + when @program.naked_annotation node.naked = true - when @program.returns_twice + when @program.returns_twice_annotation node.returns_twice = true - when @program.raises + when @program.raises_annotation node.raises = true else yield annotation_type, ann diff --git a/src/compiler/crystal/semantic/type_declaration_visitor.cr b/src/compiler/crystal/semantic/type_declaration_visitor.cr index fd4a9ce6f7a3..e24869c8368e 100644 --- a/src/compiler/crystal/semantic/type_declaration_visitor.cr +++ b/src/compiler/crystal/semantic/type_declaration_visitor.cr @@ -95,7 +95,7 @@ class Crystal::TypeDeclarationVisitor < Crystal::SemanticVisitor def visit(node : ExternalVar) thread_local = false process_annotations do |ann| - thread_local = true if ann == @program.thread_local + thread_local = true if ann == @program.thread_local_annotation end var_type = lookup_type(node.type_spec) From c3eb6d217039a0efff065370436bbcc345be4e7a Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Tue, 8 May 2018 06:02:15 -0300 Subject: [PATCH 06/16] Annotations: add spec for overriding an annotation usage --- spec/compiler/semantic/annotation_spec.cr | 48 +++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/spec/compiler/semantic/annotation_spec.cr b/spec/compiler/semantic/annotation_spec.cr index c8895603e8c0..c26b86f01422 100644 --- a/spec/compiler/semantic/annotation_spec.cr +++ b/spec/compiler/semantic/annotation_spec.cr @@ -238,4 +238,52 @@ describe "Semantic: annotation" do Moo.new(1).foo )) { int32 } end + + it "overrides annotation value in type" do + assert_type(%( + annotation Foo + end + + @[Foo(1)] + module Moo + end + + @[Foo(2)] + module Moo + end + + {% if Moo.annotation(Foo)[0] == 2 %} + 1 + {% else %} + 'a' + {% end %} + )) { int32 } + end + + it "overrides annotation in instance var" do + assert_type(%( + annotation Foo + end + + class Moo + @[Foo(1)] + @x : Int32 = 1 + end + + class Moo + @[Foo(2)] + @x : Int32 = 1 + + def foo + {% if @type.instance_vars.first.annotation(Foo)[0] == 2 %} + 1 + {% else %} + 'a' + {% end %} + end + end + + Moo.new.foo + )) { int32 } + end end From c3e6c04e8f27c26e85f25000e64ef08e38a56f60 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Tue, 8 May 2018 06:07:46 -0300 Subject: [PATCH 07/16] Test non-existent annotations, or wrong usage of annotation --- spec/compiler/semantic/annotation_spec.cr | 18 ++++++++++++++++++ .../crystal/semantic/semantic_visitor.cr | 4 ---- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/spec/compiler/semantic/annotation_spec.cr b/spec/compiler/semantic/annotation_spec.cr index c26b86f01422..e1bd3d5eeff7 100644 --- a/spec/compiler/semantic/annotation_spec.cr +++ b/spec/compiler/semantic/annotation_spec.cr @@ -286,4 +286,22 @@ describe "Semantic: annotation" do Moo.new.foo )) { int32 } end + + it "errors if annotation doesn't exist" do + assert_error %( + @[DoesntExist] + class Moo + end + ), + "undefined constant DoesntExist" + end + + it "errors if annotation doesn't point to an annotation type" do + assert_error %( + @[Int32] + class Moo + end + ), + "Int32 is not an annotation, it's a struct" + end end diff --git a/src/compiler/crystal/semantic/semantic_visitor.cr b/src/compiler/crystal/semantic/semantic_visitor.cr index af1de363081c..5ed9291d6399 100644 --- a/src/compiler/crystal/semantic/semantic_visitor.cr +++ b/src/compiler/crystal/semantic/semantic_visitor.cr @@ -434,10 +434,6 @@ abstract class Crystal::SemanticVisitor < Crystal::Visitor type = lookup_type(ann.path) end - unless type - ann.raise "undefined annotation #{ann.path}" - end - unless type.is_a?(AnnotationType) ann.raise "#{ann.path} is not an annotation, it's a #{type.type_desc}" end From 4413d66586055ba0bdf7a3839c2ad3c2033fac28 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Tue, 8 May 2018 06:13:30 -0300 Subject: [PATCH 08/16] Error if using annotation other than ThreadLocal for class vars --- spec/compiler/semantic/annotation_spec.cr | 13 ++++++++ src/compiler/crystal/semantic/main_visitor.cr | 31 ++++--------------- .../crystal/semantic/semantic_visitor.cr | 12 +++++++ .../semantic/type_declaration_visitor.cr | 5 +-- 4 files changed, 32 insertions(+), 29 deletions(-) diff --git a/spec/compiler/semantic/annotation_spec.cr b/spec/compiler/semantic/annotation_spec.cr index e1bd3d5eeff7..a0b9396d6251 100644 --- a/spec/compiler/semantic/annotation_spec.cr +++ b/spec/compiler/semantic/annotation_spec.cr @@ -304,4 +304,17 @@ describe "Semantic: annotation" do ), "Int32 is not an annotation, it's a struct" end + + it "errors if using annotation other than ThreadLocal for class vars" do + assert_error %( + annotation Foo + end + + class Moo + @[Foo] + @@x = 0 + end + ), + "class variables can only be annotated with ThreadLocal" + end end diff --git a/src/compiler/crystal/semantic/main_visitor.cr b/src/compiler/crystal/semantic/main_visitor.cr index a2180db9f5fe..012fde1b402d 100644 --- a/src/compiler/crystal/semantic/main_visitor.cr +++ b/src/compiler/crystal/semantic/main_visitor.cr @@ -415,10 +415,7 @@ module Crystal node.raise "declaring the type of a class variable must be done at the class level" end - thread_local = false - process_annotations do |ann| - thread_local = true if ann == @program.thread_local_annotation - end + thread_local = check_class_var_annotations class_var = lookup_class_var(var) var.var = class_var @@ -428,11 +425,7 @@ module Crystal node.raise "declaring the type of a global variable must be done at the class level" end - thread_local = false - process_annotations do |ann| - thread_local = true if ann == @program.thread_local_annotation - end - + thread_local = check_class_var_annotations if thread_local global_var = @program.global_vars[var.name] global_var.thread_local = true @@ -517,10 +510,7 @@ module Crystal node.raise "can only declare instance variables of a non-generic class, not a #{type.type_desc} (#{type})" end when ClassVar - thread_local = false - process_annotations do |ann| - thread_local = true if ann == @program.thread_local_annotation - end + thread_local = check_class_var_annotations class_var = visit_class_var var class_var.thread_local = true if thread_local @@ -687,10 +677,7 @@ module Crystal end def visit(node : ClassVar) - thread_local = false - process_annotations do |ann| - thread_local = true if ann == @program.thread_local_annotation - end + thread_local = check_class_var_annotations var = visit_class_var node var.thread_local = true if thread_local @@ -884,10 +871,7 @@ module Crystal end def type_assign(target : Global, value, node) - thread_local = false - process_annotations do |ann| - thread_local = true if ann == @program.thread_local_annotation - end + thread_local = check_class_var_annotations value.accept self @@ -910,10 +894,7 @@ module Crystal end def type_assign(target : ClassVar, value, node) - thread_local = false - process_annotations do |ann| - thread_local = true if ann == @program.thread_local_annotation - end + thread_local = check_class_var_annotations # Outside a def is already handled by ClassVarsInitializerVisitor # (@exp_nest is 1 if we are at the top level because it was incremented diff --git a/src/compiler/crystal/semantic/semantic_visitor.cr b/src/compiler/crystal/semantic/semantic_visitor.cr index 5ed9291d6399..323b3bb50079 100644 --- a/src/compiler/crystal/semantic/semantic_visitor.cr +++ b/src/compiler/crystal/semantic/semantic_visitor.cr @@ -441,6 +441,18 @@ abstract class Crystal::SemanticVisitor < Crystal::Visitor type end + def check_class_var_annotations + thread_local = false + process_annotations do |annotation_type, ann| + if annotation_type == @program.thread_local_annotation + thread_local = true + else + ann.raise "class variables can only be annotated with ThreadLocal" + end + end + thread_local + end + def check_allowed_in_lib(node, type = node.type.instance_type) unless type.allowed_in_lib? msg = String.build do |msg| diff --git a/src/compiler/crystal/semantic/type_declaration_visitor.cr b/src/compiler/crystal/semantic/type_declaration_visitor.cr index e24869c8368e..bfd89aac7e71 100644 --- a/src/compiler/crystal/semantic/type_declaration_visitor.cr +++ b/src/compiler/crystal/semantic/type_declaration_visitor.cr @@ -93,10 +93,7 @@ class Crystal::TypeDeclarationVisitor < Crystal::SemanticVisitor end def visit(node : ExternalVar) - thread_local = false - process_annotations do |ann| - thread_local = true if ann == @program.thread_local_annotation - end + thread_local = check_class_var_annotations var_type = lookup_type(node.type_spec) var_type = check_allowed_in_lib node.type_spec, var_type From 39e577d2a1484bcea156af76713bd897162ce5e3 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Tue, 8 May 2018 06:17:40 -0300 Subject: [PATCH 09/16] Give error when using invalid annotation on defs or funs --- spec/compiler/semantic/annotation_spec.cr | 26 +++++++++++++++++++ .../crystal/semantic/top_level_visitor.cr | 4 +++ 2 files changed, 30 insertions(+) diff --git a/spec/compiler/semantic/annotation_spec.cr b/spec/compiler/semantic/annotation_spec.cr index a0b9396d6251..69fe4aeb05ac 100644 --- a/spec/compiler/semantic/annotation_spec.cr +++ b/spec/compiler/semantic/annotation_spec.cr @@ -317,4 +317,30 @@ describe "Semantic: annotation" do ), "class variables can only be annotated with ThreadLocal" end + + it "errors if using invalid annotation on def" do + assert_error %( + annotation Foo + end + + class Moo + @[Foo] + def foo + end + end + ), + "methods can only be annotated with: NoInline, AlwaysInline, Naked, ReturnsTwice, Raises, Primitive" + end + + it "errors if using invalid annotation on fun" do + assert_error %( + annotation Foo + end + + @[Foo] + fun foo : Void + end + ), + "funs can only be annotated with: NoInline, AlwaysInline, Naked, ReturnsTwice, Raises, CallConvention" + end end diff --git a/src/compiler/crystal/semantic/top_level_visitor.cr b/src/compiler/crystal/semantic/top_level_visitor.cr index 92ec1d15999d..4a3b909402ca 100644 --- a/src/compiler/crystal/semantic/top_level_visitor.cr +++ b/src/compiler/crystal/semantic/top_level_visitor.cr @@ -316,6 +316,8 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor process_def_annotations(node) do |annotation_type, ann| if annotation_type == @program.primitive_annotation process_primitive_annotation(node, ann) + else + ann.raise "methods can only be annotated with: NoInline, AlwaysInline, Naked, ReturnsTwice, Raises, Primitive" end end @@ -807,6 +809,8 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor process_def_annotations(external) do |annotation_type, ann| if annotation_type == @program.call_convention_annotation call_convention = parse_call_convention(ann, call_convention) + else + ann.raise "funs can only be annotated with: NoInline, AlwaysInline, Naked, ReturnsTwice, Raises, CallConvention" end end From 6abcc2550ede1e05adcf3b5d5c489ab0dc5f9fe6 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Tue, 8 May 2018 06:48:29 -0300 Subject: [PATCH 10/16] Don't carry annotations from lib to fun --- spec/compiler/semantic/annotation_spec.cr | 9 +++++++++ src/compiler/crystal/semantic/top_level_visitor.cr | 1 + 2 files changed, 10 insertions(+) diff --git a/spec/compiler/semantic/annotation_spec.cr b/spec/compiler/semantic/annotation_spec.cr index 69fe4aeb05ac..131845f61e6b 100644 --- a/spec/compiler/semantic/annotation_spec.cr +++ b/spec/compiler/semantic/annotation_spec.cr @@ -343,4 +343,13 @@ describe "Semantic: annotation" do ), "funs can only be annotated with: NoInline, AlwaysInline, Naked, ReturnsTwice, Raises, CallConvention" end + + it "doesn't carry link attribute from lib to fun" do + semantic(%( + @[Link("foo")] + lib LibFoo + fun foo + end + )) + end end diff --git a/src/compiler/crystal/semantic/top_level_visitor.cr b/src/compiler/crystal/semantic/top_level_visitor.cr index 4a3b909402ca..6acb0ed17a5d 100644 --- a/src/compiler/crystal/semantic/top_level_visitor.cr +++ b/src/compiler/crystal/semantic/top_level_visitor.cr @@ -429,6 +429,7 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor check_outside_exp node, "declare lib" annotations = @annotations + @annotations = nil scope = current_type_scope(node) From b56d76bf93f3eadb3670f3ff078dae3520a27c0b Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Tue, 8 May 2018 07:44:23 -0300 Subject: [PATCH 11/16] Clear annotations before processing inner type. Also let some methods receive an explicit argument instead of a default one. --- .../crystal/semantic/semantic_visitor.cr | 4 +-- .../crystal/semantic/top_level_visitor.cr | 36 +++++++++++-------- .../semantic/type_declaration_visitor.cr | 2 +- .../crystal/semantic/type_guess_visitor.cr | 2 +- 4 files changed, 26 insertions(+), 18 deletions(-) diff --git a/src/compiler/crystal/semantic/semantic_visitor.cr b/src/compiler/crystal/semantic/semantic_visitor.cr index 323b3bb50079..d5e8b9862969 100644 --- a/src/compiler/crystal/semantic/semantic_visitor.cr +++ b/src/compiler/crystal/semantic/semantic_visitor.cr @@ -412,7 +412,7 @@ abstract class Crystal::SemanticVisitor < Crystal::Visitor node.raise "expanding macro", ex end - def process_annotations(annotations = @annotations) + def process_annotations(annotations) annotations.try &.each do |ann| yield lookup_annotation(ann), ann end @@ -443,7 +443,7 @@ abstract class Crystal::SemanticVisitor < Crystal::Visitor def check_class_var_annotations thread_local = false - process_annotations do |annotation_type, ann| + process_annotations(@annotations) do |annotation_type, ann| if annotation_type == @program.thread_local_annotation thread_local = true else diff --git a/src/compiler/crystal/semantic/top_level_visitor.cr b/src/compiler/crystal/semantic/top_level_visitor.cr index 6acb0ed17a5d..31efb5d87d40 100644 --- a/src/compiler/crystal/semantic/top_level_visitor.cr +++ b/src/compiler/crystal/semantic/top_level_visitor.cr @@ -47,6 +47,7 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor scope, name, type = lookup_type_def(node) annotations = @annotations + @annotations = nil created_new_type = false @@ -208,6 +209,7 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor check_outside_exp node, "declare module" annotations = @annotations + @annotations = nil scope, name, type = lookup_type_def(node) @@ -312,8 +314,9 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor check_outside_exp node, "declare def" annotations = @annotations + @annotations = nil - process_def_annotations(node) do |annotation_type, ann| + process_def_annotations(node, annotations) do |annotation_type, ann| if annotation_type == @program.primitive_annotation process_primitive_annotation(node, ann) else @@ -321,7 +324,7 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor end end - node.doc ||= annotations_doc() + node.doc ||= annotations_doc(annotations) check_ditto node is_instance_method = false @@ -464,10 +467,12 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor end def visit(node : CStructOrUnionDef) - packed = false + annotations = @annotations + @annotations = nil + packed = false unless node.union? - process_annotations do |ann| + process_annotations(annotations) do |ann| packed = true if ann == @program.packed_annotation end end @@ -518,8 +523,9 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor check_outside_exp node, "declare enum" annotations = @annotations + @annotations = nil - annotations_doc = annotations_doc() + annotations_doc = annotations_doc(annotations) scope, name, enum_type = lookup_type_def(node) @@ -544,7 +550,7 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor enum_type.private = true if node.visibility.private? - process_annotations do |annotation_type, ann| + process_annotations(annotations) do |annotation_type, ann| enum_type.flags = true if annotation_type == @program.flags_annotation enum_type.add_annotation(annotation_type, ann) end @@ -552,8 +558,7 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor node.resolved_type = enum_type attach_doc enum_type, node - enum_type.doc ||= annotations_doc - @annotations = nil + enum_type.doc ||= annotations_doc(annotations) pushing_type(enum_type) do counter = enum_type.flags? ? 1 : 0 @@ -804,10 +809,13 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor node.raise "can only declare fun at lib or global scope" end + annotations = @annotations + @annotations = nil + external = External.new(node.name, ([] of Arg), node.body, node.real_name).at(node) call_convention = nil - process_def_annotations(external) do |annotation_type, ann| + process_def_annotations(external, annotations) do |annotation_type, ann| if annotation_type == @program.call_convention_annotation call_convention = parse_call_convention(ann, call_convention) else @@ -815,7 +823,7 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor end end - node.doc ||= annotations_doc() + node.doc ||= annotations_doc(annotations) check_ditto node # Copy call convention from lib, if any @@ -1009,12 +1017,12 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor @last_doc = node.doc end - def annotations_doc - @annotations.try(&.first?).try &.doc + def annotations_doc(annotations) + annotations.try(&.first?).try &.doc end - def process_def_annotations(node) - process_annotations do |annotation_type, ann| + def process_def_annotations(node, annotations) + process_annotations(annotations) do |annotation_type, ann| case annotation_type when @program.no_inline_annotation node.no_inline = true diff --git a/src/compiler/crystal/semantic/type_declaration_visitor.cr b/src/compiler/crystal/semantic/type_declaration_visitor.cr index bfd89aac7e71..f088704cd946 100644 --- a/src/compiler/crystal/semantic/type_declaration_visitor.cr +++ b/src/compiler/crystal/semantic/type_declaration_visitor.cr @@ -225,7 +225,7 @@ class Crystal::TypeDeclarationVisitor < Crystal::SemanticVisitor def declare_instance_var(owner, node, var) annotations = nil - process_annotations do |annotation_type, ann| + process_annotations(@annotations) do |annotation_type, ann| annotations ||= [] of {AnnotationType, Annotation} annotations << {annotation_type, ann} end diff --git a/src/compiler/crystal/semantic/type_guess_visitor.cr b/src/compiler/crystal/semantic/type_guess_visitor.cr index 77fad6f49e9d..f46675d6316a 100644 --- a/src/compiler/crystal/semantic/type_guess_visitor.cr +++ b/src/compiler/crystal/semantic/type_guess_visitor.cr @@ -398,7 +398,7 @@ module Crystal def add_instance_var_type_info(vars, name, type : Type, node) annotations = nil - process_annotations do |annotation_type, ann| + process_annotations(@annotations) do |annotation_type, ann| annotations ||= [] of {AnnotationType, Annotation} annotations << {annotation_type, ann} end From 1009e07424f88f19bc3a1f7b5cd81bb8b103cf7d Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Tue, 8 May 2018 07:49:36 -0300 Subject: [PATCH 12/16] Typo: remove incorrect plural --- src/compiler/crystal/codegen/link.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/crystal/codegen/link.cr b/src/compiler/crystal/codegen/link.cr index bb454a07ff63..a9f51eb219d7 100644 --- a/src/compiler/crystal/codegen/link.cr +++ b/src/compiler/crystal/codegen/link.cr @@ -171,8 +171,8 @@ module Crystal types.try &.each_value do |type| next if type.is_a?(AliasType) || type.is_a?(TypeDefType) - if type.is_a?(LibType) && type.used? && (links_annotations = type.link_annotations) - annotations.concat links_annotations + if type.is_a?(LibType) && type.used? && (link_annotations = type.link_annotations) + annotations.concat link_annotations end add_link_annotations type.types?, annotations From 5aedf7b1225f764a7db19ee7da661e35feb33143 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Tue, 8 May 2018 18:10:04 -0300 Subject: [PATCH 13/16] Refactor macro method "annotation" implementation --- src/compiler/crystal/macros/methods.cr | 44 ++++++++++++-------------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/src/compiler/crystal/macros/methods.cr b/src/compiler/crystal/macros/methods.cr index f63c9afad780..22212239ebdd 100644 --- a/src/compiler/crystal/macros/methods.cr +++ b/src/compiler/crystal/macros/methods.cr @@ -1070,18 +1070,8 @@ module Crystal BoolLiteral.new(!!default_value) end when "annotation" - interpret_one_arg_method(method, args) do |arg| - unless arg.is_a?(TypeNode) - args[0].raise "argument to 'MetaVar#annotation' must be a TypeNode, not #{arg.class_desc}'" - end - - type = arg.type - unless type.is_a?(AnnotationType) - args[0].raise "argument to 'MetaVar#annotation' must be an annotation type , not #{type} (#{type.type_desc})'" - end - - value = self.var.annotation(type) - value || NilLiteral.new + fetch_annotation(self, method, args) do |type| + self.var.annotation(type) end else super @@ -1464,18 +1454,8 @@ module Crystal BoolLiteral.new(!!type.has_attribute?(value)) end when "annotation" - interpret_one_arg_method(method, args) do |arg| - unless arg.is_a?(TypeNode) - args[0].raise "argument to 'TypeNode#annotation' must be a TypeNode, not #{arg.class_desc}'" - end - - type = arg.type - unless type.is_a?(AnnotationType) - args[0].raise "argument to 'TypeNode#annotation' must be an annotation type , not #{type} (#{type.type_desc})'" - end - - value = self.type.annotation(type) - value || NilLiteral.new + fetch_annotation(self, method, args) do |type| + self.type.annotation(type) end when "size" interpret_argless_method(method, args) do @@ -2260,3 +2240,19 @@ def filter(object, klass, block, interpreter, keep = true) keep ? block_result : !block_result end) end + +private def fetch_annotation(node, method, args) + node.interpret_one_arg_method(method, args) do |arg| + unless arg.is_a?(Crystal::TypeNode) + args[0].raise "argument to '#{node.class_desc}#annotation' must be a TypeNode, not #{arg.class_desc}'" + end + + type = arg.type + unless type.is_a?(Crystal::AnnotationType) + args[0].raise "argument to '#{node.class_desc}#annotation' must be an annotation type , not #{type} (#{type.type_desc})'" + end + + value = yield type + value || Crystal::NilLiteral.new + end +end From 4da09a2ff7d17873784c89b3938f210a83f66001 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Tue, 8 May 2018 18:10:21 -0300 Subject: [PATCH 14/16] Mark macro method helper as private --- src/compiler/crystal/macros/methods.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/crystal/macros/methods.cr b/src/compiler/crystal/macros/methods.cr index 22212239ebdd..3385878d5570 100644 --- a/src/compiler/crystal/macros/methods.cr +++ b/src/compiler/crystal/macros/methods.cr @@ -2231,7 +2231,7 @@ private def empty_no_return_array Crystal::ArrayLiteral.new(of: Crystal::Path.global("NoReturn")) end -def filter(object, klass, block, interpreter, keep = true) +private def filter(object, klass, block, interpreter, keep = true) block_arg = block.args.first? klass.new(object.elements.select do |elem| From e6a6a1e8c0dd06a2cc05c70d3dac056d8c1461e1 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Tue, 8 May 2018 18:12:48 -0300 Subject: [PATCH 15/16] Allow annotations on methods --- spec/compiler/semantic/annotation_spec.cr | 13 +++++++++---- src/compiler/crystal/macros.cr | 5 +++++ src/compiler/crystal/macros/methods.cr | 4 ++++ src/compiler/crystal/semantic/ast.cr | 14 ++++++++++++++ src/compiler/crystal/semantic/top_level_visitor.cr | 4 ++-- 5 files changed, 34 insertions(+), 6 deletions(-) diff --git a/spec/compiler/semantic/annotation_spec.cr b/spec/compiler/semantic/annotation_spec.cr index 131845f61e6b..6c8077ef5b44 100644 --- a/spec/compiler/semantic/annotation_spec.cr +++ b/spec/compiler/semantic/annotation_spec.cr @@ -318,8 +318,8 @@ describe "Semantic: annotation" do "class variables can only be annotated with ThreadLocal" end - it "errors if using invalid annotation on def" do - assert_error %( + it "adds annotation on def" do + assert_type(%( annotation Foo end @@ -328,8 +328,13 @@ describe "Semantic: annotation" do def foo end end - ), - "methods can only be annotated with: NoInline, AlwaysInline, Naked, ReturnsTwice, Raises, Primitive" + + {% if Moo.methods.first.annotation(Foo) %} + 1 + {% else %} + 'a' + {% end %} + )) { int32 } end it "errors if using invalid annotation on fun" do diff --git a/src/compiler/crystal/macros.cr b/src/compiler/crystal/macros.cr index 4b75bbe066bd..40e7a6fc31fd 100644 --- a/src/compiler/crystal/macros.cr +++ b/src/compiler/crystal/macros.cr @@ -1054,6 +1054,11 @@ module Crystal::Macros # Returns the visibility of this def: `:public`, `:protected` or `:private`. def visibility : SymbolLiteral end + + # Returns any `Annotation` with the given `type` + # attached to this method. + def annotation(type : TypeNode) : Annotation + end end # A macro definition. diff --git a/src/compiler/crystal/macros/methods.cr b/src/compiler/crystal/macros/methods.cr index 3385878d5570..724d27aaf2ee 100644 --- a/src/compiler/crystal/macros/methods.cr +++ b/src/compiler/crystal/macros/methods.cr @@ -1252,6 +1252,10 @@ module Crystal interpret_argless_method(method, args) do visibility_to_symbol(@visibility) end + when "annotation" + fetch_annotation(self, method, args) do |type| + self.annotation(type) + end else super end diff --git a/src/compiler/crystal/semantic/ast.cr b/src/compiler/crystal/semantic/ast.cr index a93d3ae3baa1..c22d7c8a0792 100644 --- a/src/compiler/crystal/semantic/ast.cr +++ b/src/compiler/crystal/semantic/ast.cr @@ -139,6 +139,9 @@ module Crystal # Is this a `new` method that was expanded from an initialize? property? new = false + # Annotations on this def + property annotations : Hash(AnnotationType, Annotation)? + @macro_owner : Type? def macro_owner=(@macro_owner) @@ -168,6 +171,17 @@ module Crystal end end + # Adds an annotation with the given type and value + def add_annotation(annotation_type : AnnotationType, value : Annotation) + annotations = @annotations ||= {} of AnnotationType => Annotation + annotations[annotation_type] = value + end + + # Returns the annotation with the given type, if any, or nil otherwise + def annotation(annotation_type) : Annotation? + @annotations.try &.[annotation_type] + end + # Returns the minimum and maximum number of arguments that must # be passed to this method. def min_max_args_sizes diff --git a/src/compiler/crystal/semantic/top_level_visitor.cr b/src/compiler/crystal/semantic/top_level_visitor.cr index 31efb5d87d40..f89c41dc34db 100644 --- a/src/compiler/crystal/semantic/top_level_visitor.cr +++ b/src/compiler/crystal/semantic/top_level_visitor.cr @@ -319,9 +319,9 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor process_def_annotations(node, annotations) do |annotation_type, ann| if annotation_type == @program.primitive_annotation process_primitive_annotation(node, ann) - else - ann.raise "methods can only be annotated with: NoInline, AlwaysInline, Naked, ReturnsTwice, Raises, Primitive" end + + node.add_annotation(annotation_type, ann) end node.doc ||= annotations_doc(annotations) From 615adac4516062da64b19ffa94bbd9477885b589 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Tue, 8 May 2018 18:27:50 -0300 Subject: [PATCH 16/16] In macros, `annotation` nests --- spec/compiler/lexer/lexer_macro_spec.cr | 2 +- src/compiler/crystal/syntax/lexer.cr | 23 +++++++++++++++-------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/spec/compiler/lexer/lexer_macro_spec.cr b/spec/compiler/lexer/lexer_macro_spec.cr index 1b3d4bc3cf3c..a8eb7bf4255b 100644 --- a/spec/compiler/lexer/lexer_macro_spec.cr +++ b/spec/compiler/lexer/lexer_macro_spec.cr @@ -39,7 +39,7 @@ describe "Lexer macro" do token.type.should eq(:MACRO_END) end - ["begin", "do", "if", "unless", "class", "struct", "module", "def", "while", "until", "case", "macro", "fun", "lib", "union"].each do |keyword| + ["begin", "do", "if", "unless", "class", "struct", "module", "def", "while", "until", "case", "macro", "fun", "lib", "union", "annotation"].each do |keyword| it "lexes macro with nested #{keyword}" do lexer = Lexer.new(%(hello\n #{keyword} {{world}} end end)) diff --git a/src/compiler/crystal/syntax/lexer.cr b/src/compiler/crystal/syntax/lexer.cr index 21709c89d66b..ac5c599927ee 100644 --- a/src/compiler/crystal/syntax/lexer.cr +++ b/src/compiler/crystal/syntax/lexer.cr @@ -2283,15 +2283,22 @@ module Crystal def check_macro_opening_keyword(beginning_of_line) case char = current_char when 'a' - if next_char == 'b' && next_char == 's' && next_char == 't' && next_char == 'r' && next_char == 'a' && next_char == 'c' && next_char == 't' && next_char.whitespace? - case next_char - when 'd' - next_char == 'e' && next_char == 'f' && peek_not_ident_part_or_end_next_char && :abstract_def - when 'c' - next_char == 'l' && next_char == 'a' && next_char == 's' && next_char == 's' && peek_not_ident_part_or_end_next_char && :abstract_class - when 's' - next_char == 't' && next_char == 'r' && next_char == 'u' && next_char == 'c' && next_char == 't' && peek_not_ident_part_or_end_next_char && :abstract_struct + case next_char + when 'b' + if next_char == 's' && next_char == 't' && next_char == 'r' && next_char == 'a' && next_char == 'c' && next_char == 't' && next_char.whitespace? + case next_char + when 'd' + next_char == 'e' && next_char == 'f' && peek_not_ident_part_or_end_next_char && :abstract_def + when 'c' + next_char == 'l' && next_char == 'a' && next_char == 's' && next_char == 's' && peek_not_ident_part_or_end_next_char && :abstract_class + when 's' + next_char == 't' && next_char == 'r' && next_char == 'u' && next_char == 'c' && next_char == 't' && peek_not_ident_part_or_end_next_char && :abstract_struct + else + false + end end + when 'n' + next_char == 'n' && next_char == 'o' && next_char == 't' && next_char == 'a' && next_char == 't' && next_char == 'i' && next_char == 'o' && next_char == 'n' && peek_not_ident_part_or_end_next_char && :annotation end when 'b' next_char == 'e' && next_char == 'g' && next_char == 'i' && next_char == 'n' && peek_not_ident_part_or_end_next_char && :begin