diff --git a/docs/syntax_and_semantics/annotations/README.md b/docs/syntax_and_semantics/annotations/README.md index 09f398d61..bb9b515a8 100644 --- a/docs/syntax_and_semantics/annotations/README.md +++ b/docs/syntax_and_semantics/annotations/README.md @@ -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`. @@ -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 @@ -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 @@ -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. @@ -197,6 +212,9 @@ end annotation MyIvar end +annotation MyParameter +end + @[MyClass] class Foo pp {{ @type.annotation(MyClass).stringify }} @@ -219,8 +237,22 @@ 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 @@ -228,9 +260,14 @@ pp {{ Foo.annotation(MyClass).stringify }} "@[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)`. diff --git a/docs/syntax_and_semantics/macros/README.md b/docs/syntax_and_semantics/macros/README.md index 82b486b84..92516c7fa 100644 --- a/docs/syntax_and_semantics/macros/README.md +++ b/docs/syntax_and_semantics/macros/README.md @@ -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: