iyi

Playground

samples/iyi/derive.iyi

derive, and where it runs (SPEC.md R-5, II.4).

samples/iyi/derive.iyi · iyi-derive.wasm · 124 KB
# `derive`, and where it runs (SPEC.md R-5, II.4).
#
#   ./bin/iyi run samples/iyi/derive.iyi
#
# A derive runs once, in the module that declares the type. What it generates is
# an ordinary declaration of that module, so it travels in the module's
# `.iyimod` and a consumer calls the derived method without running the macro
# and without this module's source. `bench/samples_roundtrip.sh` builds this
# sample from its artifacts with `std/derives` deleted, which is that claim
# taken literally.
#
# The macro is handed the facts R-5 lets it read: the declaration's name, and
# the fields written in its body, each with the type it was written as. It
# cannot enumerate a type it was not given, which is what keeps a derive
# bounded and cacheable.

module samples/derive

import std/derives::*

pub struct Point
  @x : Int32
  @y : Int32

  def initialize(@x : Int32, @y : Int32)
  end

  derive described
end

# One derive, two declarations. The macro reads each one's own fields, so
# neither type had to be written down inside the macro.
pub struct Span
  @from : Int32
  @to : Int32
  @label : String

  def initialize(@from : Int32, @to : Int32, @label : String)
  end

  derive described
end

puts Point.new(3, 4).described
puts Span.new(1, 9, "range").described

# The field type is a type, not a name, so a derive can ask what it implements.
# `Name` and `Show` both belong to `std/derives`, and both arrive from that
# module's artifact when its source is gone: `@label` is rendered through the
# trait, and `@count` is skipped because `Int32` does not implement it.
#
# `getter label : Name` is a macro call, so the field it declares exists only
# once it has run. A derive reads the declarations above it, which is why
# `derive` goes last. Below `getter`, this would be an error rather than a
# method generated over no fields.
#
# One line, two derives: each macro reads the same declaration and adds its own
# methods.
pub struct Tag
  getter label : Name
  @count : Int32

  def initialize(@label : Name, @count : Int32)
  end

  derive described, shown
end

puts Tag.new(Name.new("hi"), 2).described
puts Tag.new(Name.new("hi"), 2).shown

# ---------------------------------------------------------------------------
# What this sample would report as an error
# ---------------------------------------------------------------------------
#
# * `derive missing` where nothing exports a `missing` macro. The error names
#   the macro and the declaration it would have to be written as, rather than
#   failing as an undefined method on the type's metaclass.
#
# * `derive` outside a class or struct body. There is no declaration to read,
#   so there is nothing for the macro to be handed.
#
# * A `derive` inside a reopened type from another module. That is refused
#   before the derive is reached, by R-3: iyi has no open classes, so a module
#   cannot add to a type it does not declare.
#
# * A `getter` (or any macro call that declares a field) written below the
#   `derive` that should read it. The derive names the call and says which way
#   to move, rather than generating a method over the fields it happened to see.
output
Press Run.

recorded with iyi 0.18.0 (built on Crystal 1.22.0-dev [2a9653052] (2026-10-18)) · Linux 7.0.0-38-generic x86_64, QEMU Virtual CPU version 2.5+ · 2026-10-19

You can type in the pane: nothing here compiles, so what you type cannot run, and the page says which bytes it is running the moment the text stops being the recorded file. There is no standard input, no filesystem and no threads on this target, and one sample fails on purpose for exactly that reason.

Run executes iyi-derive.wasm, the module the compiler built from this file, in your browser. How this works

Try it on your machine

Copy the program into derive.iyi, change something, and run it. Only a compiler on your own machine can run what you wrote.

sh, after installing
$ iyi run derive.iyi