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

Document the new Annotation methods #354

Merged
merged 3 commits into from
Aug 20, 2019
Merged
Changes from all commits
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
89 changes: 79 additions & 10 deletions syntax_and_semantics/annotations.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,46 @@ end
@[MyAnnotation(value: 2)]
def annotation_value
# The name can be a `String`, `Symbol`, or `MacroId`
{{@def.annotation(MyAnnotation)[:value]}}
{{ @def.annotation(MyAnnotation)[:value] }}
end

annotation_value # => 2
```

The `named_args` method can be used to read all key/value pairs on an annotation as a `NamedTupleLiteral`. This method is defined on all annotations by default, and is unique to each applied annotation.

```crystal
annotation MyAnnotation
end

@[MyAnnotation(value: 2, name: "Jim")]
def annotation_named_args
{{ @def.annotation(MyAnnotation).named_args }}
end

annotation_named_args # => {value: 2, name: "Jim"}
```

Since this method returns a `NamedTupleLiteral`, all of the [methods](https://crystal-lang.org/api/Crystal/Macros/NamedTupleLiteral.html) on that type are available for use. Especially `#double_splat` which makes it easy to pass annotation arguments to methods.

```crystal
annotation MyAnnotation
end

class SomeClass
def initialize(@value : Int32, @name : String); end
end

@[MyAnnotation(value: 2, name: "Jim")]
def new_test
{% begin %}
SomeClass.new {{ @def.annotation(MyAnnotation).named_args.double_splat }}
{% end %}
end

new_test # => #<SomeClass:0x5621a19ddf00 @name="Jim", @value=2>
```

### Positional

Positional values can be accessed at compile time via the [`[]`](<https://crystal-lang.org/api/Crystal/Macros/Annotation.html#%5B%5D%28index%3ANumberLiteral%29%3AASTNode-instance-method>) method; however, only one index can be accessed at a time.
Expand All @@ -86,11 +120,11 @@ Positional values can be accessed at compile time via the [`[]`](<https://crysta
annotation MyAnnotation
end

@[MyAnnotation(1,2,3,4)]
@[MyAnnotation(1, 2, 3, 4)]
def annotation_read
{% for idx in [0,1,2,3,4] %}
{% for idx in [0, 1, 2, 3, 4] %}
{% value = @def.annotation(MyAnnotation)[idx] %}
pp "{{idx}} = {{value}}"
pp "{{ idx }} = {{ value }}"
{% end %}
end

Expand All @@ -104,6 +138,42 @@ annotation_read
"4 = nil"
```

The `args` method can be used to read all positional arguments on an annotation as a `TupleLiteral`. This method is defined on all annotations by default, and is unique to each applied annotation.

```crystal
annotation MyAnnotation
end

@[MyAnnotation(1, 2, 3, 4)]
def annotation_args
{{ @def.annotation(MyAnnotation).args }}
end

annotation_args # => {1, 2, 3, 4}
```

Since the return type of `TupleLiteral` is iterable, we can rewrite the previous example in a better way. By extension, all of the [methods](https://crystal-lang.org/api/Crystal/Macros/TupleLiteral.html) on `TupleLiteral` are available for use as well.

```crystal
annotation MyAnnotation
end

@[MyAnnotation(1, "foo", true, 17.0)]
def annotation_read
{% for value, idx in @def.annotation(MyAnnotation).args %}
pp "{{ idx }} = #{{{ value }}}"
{% end %}
end

annotation_read

# Which would print
"0 = 1"
"1 = foo"
"2 = true"
"3 = 17.0"
```

## Reading

Annotations can be read off of a [`TypeNode`](https://crystal-lang.org/api/Crystal/Macros/TypeNode.html), [`Def`](https://crystal-lang.org/api/Crystal/Macros/Def.html), or [`MetaVar`](https://crystal-lang.org/api/Crystal/Macros/MetaVar.html) using the `.annotation(type : TypeNode)` method. This method return an [`Annotation`](https://crystal-lang.org/api/master/Crystal/Macros/Annotation.html) object representing the applied annotation of the supplied type.
Expand All @@ -128,7 +198,7 @@ end

@[MyClass]
class Foo
pp {{@type.annotation(MyClass).stringify}}
pp {{ @type.annotation(MyClass).stringify }}

@[MyIvar]
@num : Int32 = 1
Expand All @@ -138,19 +208,19 @@ class Foo

def properties
{% for ivar in @type.instance_vars %}
pp {{ivar.annotation(MyIvar).stringify}}
pp {{ ivar.annotation(MyIvar).stringify }}
{% end %}
end
end

@[MyMethod]
def my_method
pp {{@def.annotation(MyMethod).stringify}}
pp {{ @def.annotation(MyMethod).stringify }}
end

Foo.new.properties
my_method
pp {{Foo.annotation(MyClass).stringify}}
pp {{ Foo.annotation(MyClass).stringify }}

# Which would print
"@[MyClass]"
Expand All @@ -173,7 +243,7 @@ end
@[MyAnnotation(123)]
def annotation_read
{% for ann, idx in @def.annotations(MyAnnotation) %}
pp "Annotation {{idx}} = {{ann[0].id}}"
pp "Annotation {{ idx }} = {{ ann[0].id }}"
{% end %}
end

Expand All @@ -184,4 +254,3 @@ annotation_read
"Annotation 1 = 123"
"Annotation 2 = 123"
```