跳到内容

注释

注释可用于向源代码中的某些功能添加元数据。类型、方法、实例变量以及方法/宏参数可以进行注释。用户定义的注释(如标准库中的 JSON::Field)使用 annotation 关键字定义。编译器提供了许多 内置注释

用户可以使用 annotation 关键字定义自己的注释,其工作方式类似于定义 classstruct

annotation MyAnnotation
end

然后可以将注释应用于各种项目,包括

  • 实例和类方法
  • 实例变量
  • 类、结构体、枚举和模块
  • 方法和宏参数(尽管后者目前无法访问)
annotation MyAnnotation
end

@[MyAnnotation]
def foo
  "foo"
end

@[MyAnnotation]
class Klass
end

@[MyAnnotation]
module MyModule
end

def method1(@[MyAnnotation] foo)
end

def method2(
  @[MyAnnotation]
  bar
)
end

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

应用

注释最适合用于存储有关给定实例变量、类型或方法的元数据,以便可以使用宏在编译时读取这些元数据。注释的主要优势之一是它们直接应用于实例变量/方法,这使得类看起来更自然,因为不需要标准宏来创建这些属性/方法。

注释的一些应用

对象序列化

有一个注释,当应用于实例变量时,会确定是否应该序列化该实例变量,或者使用什么键。Crystal 的 JSON::SerializableYAML::Serializable 就是这方面的例子。

ORM

注释可用于将属性指定为 ORM 列。除了注释之外,还可以从 TypeNode 中读取实例变量的名称和类型;从而无需任何 ORM 特定的宏。注释本身也可用于存储有关列的元数据,例如它是否可为空、列的名称或它是否是主键。

字段

数据可以存储在注释中。

annotation MyAnnotaion
end

# The fields can either be a key/value pair
@[MyAnnotation(key: "value", value: 123)]

# Or positional
@[MyAnnotation("foo", 123, false)]

键值

可以使用 [] 方法在编译时访问注释键值对的值。

annotation MyAnnotation
end

@[MyAnnotation(value: 2)]
def annotation_value
  # The name can be a `String`, `Symbol`, or `MacroId`
  {{ @def.annotation(MyAnnotation)[:value] }}
end

annotation_value # => 2

named_args 方法可用于将应用于注释的所有键值对读取为 NamedTupleLiteral。此方法默认情况下在所有注释上定义,并且对于每个应用的注释都是唯一的。

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"}

由于此方法返回 NamedTupleLiteral,因此所有 方法 都可以在该类型上使用。特别是 #double_splat,这使得将注释参数传递给方法变得容易。

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>

位置

可以使用 [] 方法在编译时访问位置值;但是,一次只能访问一个索引。

annotation MyAnnotation
end

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

annotation_read

# Which would print
"0 = 1"
"1 = 2"
"2 = 3"
"3 = 4"
"4 = nil"

args 方法可用于将应用于注释的所有位置参数读取为 TupleLiteral。此方法默认情况下在所有注释上定义,并且对于每个应用的注释都是唯一的。

annotation MyAnnotation
end

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

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

由于 TupleLiteral 的返回类型是可迭代的,因此我们可以以更好的方式重写前面的示例。通过扩展,所有 方法 都可以在 TupleLiteral 上使用。

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"

读取

可以使用 .annotation(type : TypeNode) 方法从 TypeNodeDefMetaVarArg 中读取注释。此方法返回一个 Annotation 对象,表示应用于提供的类型的注释。

注意

如果应用了相同类型的多个注释,则 .annotation 方法将返回最后一个注释。

可以使用 @type@def 变量来获取 TypeNodeDef 对象,以便在其上使用 .annotation 方法。但是,也可以使用 TypeNode 上的其他方法来获取 TypeNode/Def 类型。例如,分别使用 TypeNode.all_subclassesTypeNode.methods

提示

查看 parse_type 方法,了解获取 TypeNode 的更高级方法。

TypeNode.instance_vars 可用于获取实例变量 MetaVar 对象数组,这些对象可以用于读取在这些实例变量上定义的注释。

注意

TypeNode.instance_vars 目前仅在实例/类方法的上下文中有效。

annotation MyClass
end

annotation MyMethod
end

annotation MyIvar
end

annotation MyParameter
end

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

  @[MyIvar]
  @num : Int32 = 1

  @[MyIvar]
  property name : String = "jim"

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

@[MyMethod]
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]"

警告

注释只能从类型化块参数中读取。查看 https://github.com/crystal-lang/crystal/issues/5334

读取多个注释

#annotations 方法返回类型上所有注释的 ArrayLiteral。可选地,带有 #annotations(type : TypeNode) 方法的 TypeNode 参数仅过滤提供的类型的注释。

annotation MyAnnotation; end
annotation OtherAnnotation; end

@[MyAnnotation("foo")]
@[MyAnnotation(123)]
@[OtherAnnotation(456)]
def annotation_read
  {% for ann in @def.annotations(MyAnnotation) %}
    pp "{{ann.name}}: {{ ann[0].id }}"
  {% end %}

  puts

  {% for ann in @def.annotations %}
    pp "{{ann.name}}: {{ ann[0].id }}"
  {% end %}
end

annotation_read

# Which would print:
"MyAnnotation: foo"
"MyAnnotation: 123"

"MyAnnotation: foo"
"MyAnnotation: 123"
"OtherAnnotation: 456"