Skip to content
Merged
Show file tree
Hide file tree
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
41 changes: 39 additions & 2 deletions docs/syntax_and_semantics/annotations/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Annotations

Annotations can be used to add metadata to certain features in the source code. Types, methods and instance variables may be annotated. User-defined annotations, such as the standard library's [JSON::Field](https://crystal-lang.org/api/JSON/Field.html), are defined using the `annotation` keyword. A number of [built-in annotations](built_in_annotations.md) are provided by the compiler.
Annotations can be used to add metadata to certain features in the source code. Types, methods, instance variables, and method/macro parameters may be annotated. User-defined annotations, such as the standard library's [JSON::Field](https://crystal-lang.org/api/JSON/Field.html), are defined using the `annotation` keyword. A number of [built-in annotations](built_in_annotations.md) are provided by the compiler.

Users can define their own annotations using the `annotation` keyword, which works similarly to defining a `class` or `struct`.

Expand All @@ -14,6 +14,7 @@ The annotation can then be applied to various items, including:
* Instance and class methods
* Instance variables
* Classes, structs, enums, and modules
* Method and macro parameters (though the latter are currently unaccessable)

```crystal
annotation MyAnnotation
Expand All @@ -31,6 +32,18 @@ end
@[MyAnnotation]
module MyModule
end

def method1(@[MyAnnotation] foo)
end

def method2(
@[MyAnnotation]
bar
)
end

def method3(@[MyAnnotation] & : String ->)
end
```

## Applications
Expand Down Expand Up @@ -177,12 +190,14 @@ annotation_read

## 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/Crystal/Macros/Annotation.html) object representing the applied annotation of the supplied type.
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), [`MetaVar`](https://crystal-lang.org/api/Crystal/Macros/MetaVar.html), or [`Arg`](https://crystal-lang.org/api/Crystal/Macros/Arg.html) using the `.annotation(type : TypeNode)` method. This method return an [`Annotation`](https://crystal-lang.org/api/Crystal/Macros/Annotation.html) object representing the applied annotation of the supplied type.

NOTE: If multiple annotations of the same type are applied, the `.annotation` method will return the *last* one.

The [`@type`](../macros/#type-information) and [`@def`](../macros/#method-information) variables can be used to get a `TypeNode` or `Def` object to use the `.annotation` method on. However, it is also possible to get `TypeNode`/`Def` types using other methods on `TypeNode`. For example `TypeNode.all_subclasses` or `TypeNode.methods`, respectively.

TIP: Checkout the [`parse_type`](../macros/README.md#parse_type) method for a more advanced way to obtain a `TypeNode`.

The `TypeNode.instance_vars` can be used to get an array of instance variable `MetaVar` objects that would allow reading annotations defined on those instance variables.

NOTE: `TypeNode.instance_vars` currently only works in the context of an instance/class method.
Expand All @@ -197,6 +212,9 @@ end
annotation MyIvar
end

annotation MyParameter
end

@[MyClass]
class Foo
pp {{ @type.annotation(MyClass).stringify }}
Expand All @@ -219,18 +237,37 @@ def my_method
pp {{ @def.annotation(MyMethod).stringify }}
end

def method_params(
@[MyParameter(index: 0)]
value : Int32,
@[MyParameter(index: 1)] metadata,
@[MyParameter(index: 2)] & : -> String
)
pp {{ @def.args[0].annotation(MyParameter).stringify }}
pp {{ @def.args[1].annotation(MyParameter).stringify }}
pp {{ @def.block_arg.annotation(MyParameter).stringify }}
end

Foo.new.properties
my_method
method_params 10, false do
"foo"
end
pp {{ Foo.annotation(MyClass).stringify }}

# Which would print
"@[MyClass]"
"@[MyIvar]"
"@[MyIvar]"
"@[MyMethod]"
"@[MyParameter(index: 0)]"
"@[MyParameter(index: 1)]"
"@[MyParameter(index: 2)]"
"@[MyClass]"
```

WARNING: Annotations can only be read off of typed block parameters. See https://github.com/crystal-lang/crystal/issues/5334.

### Reading Multiple Annotations

If there are multiple annotations of the same type applied to the same instance variable/method/type, the `.annotations(type : TypeNode)` method can be used. This will work on anything that `.annotation(type : TypeNode)` would, but instead returns an `ArrayLiteral(Annotation)`.
Expand Down
19 changes: 19 additions & 0 deletions docs/syntax_and_semantics/macros/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,25 @@ end
define_method :foo, 1
```

### parse_type

Most AST nodes are obtained via either manually passed arguments, hard coded values, or retrieved from either the [type](#type-information) or [method](#method-information) information helper variables. Yet there might be cases in which a node is not directly accessible, such as if you use information from different contexts to construct the path to the desired type/constant.

In such cases the [`parse_type`](https://crystal-lang.org/api/Crystal/Macros.html#parse_type%28type_name%3AStringLiteral%29%3APath%7CGeneric%7CProcNotation%7CMetaclass-instance-method) macro method can help by parsing the provided [`StringLiteral`](https://crystal-lang.org/api/Crystal/Macros/StringLiteral.html) into something that can be resolved into the desired AST node.

```crystal
MY_CONST = 1234

struct Some::Namespace::Foo; end

{{ parse_type("Some::Namespace::Foo").resolve.struct? }} # => true
{{ parse_type("MY_CONST").resolve }} # => 1234

{{ parse_type("MissingType").resolve }} # Error: undefined constant MissingType
```

See the API docs for more examples.

## Modules and classes

Modules, classes and structs can also be generated:
Expand Down