Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

User-defined annotations #6063

Merged
merged 16 commits into from
May 8, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions spec/compiler/parser/parser_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
54 changes: 27 additions & 27 deletions spec/compiler/semantic/annotation_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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'
Expand All @@ -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'
Expand All @@ -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'
Expand All @@ -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'
Expand All @@ -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'
Expand All @@ -115,15 +115,15 @@ describe "Semantic: annotation" do

it "finds annotation in enum" do
assert_type(%(
annotation FooAnnotation
annotation Foo
end

@[Foo]
enum Moo
A = 1
end

{% if Moo.annotation(FooAnnotation) %}
{% if Moo.annotation(Foo) %}
1
{% else %}
'a'
Expand All @@ -133,15 +133,15 @@ describe "Semantic: annotation" do

it "finds annotation in lib" do
assert_type(%(
annotation FooAnnotation
annotation Foo
end

@[Foo]
lib Moo
A = 1
end

{% if Moo.annotation(FooAnnotation) %}
{% if Moo.annotation(Foo) %}
1
{% else %}
'a'
Expand All @@ -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'
Expand All @@ -172,15 +172,15 @@ describe "Semantic: annotation" do

it "finds annotation in instance var (declaration)" do
assert_type(%(
annotation FooAnnotation
annotation Foo
end

class Moo
@[Foo]
@x : Int32 = 1

def foo
{% if @type.instance_vars.first.annotation(FooAnnotation) %}
{% if @type.instance_vars.first.annotation(Foo) %}
1
{% else %}
'a'
Expand All @@ -194,15 +194,15 @@ describe "Semantic: annotation" do

it "finds annotation in instance var (assignment)" do
assert_type(%(
annotation FooAnnotation
annotation Foo
end

class Moo
@[Foo]
@x = 1

def foo
{% if @type.instance_vars.first.annotation(FooAnnotation) %}
{% if @type.instance_vars.first.annotation(Foo) %}
1
{% else %}
'a'
Expand All @@ -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)
Expand All @@ -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'
Expand Down
32 changes: 16 additions & 16 deletions src/compiler/crystal/program.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So annotations conflicts with top level types? I would have thought they are in a separate category instead. Is it temporary?

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
Expand Down Expand Up @@ -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
Expand Down
12 changes: 6 additions & 6 deletions src/compiler/crystal/semantic/main_visitor.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
23 changes: 16 additions & 7 deletions src/compiler/crystal/semantic/semantic_visitor.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Contributor

@RX14 RX14 May 5, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you put a TODO: string somewhere in this comment here so we can grep for all todos and not miss this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

# 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
Expand Down
Loading