Overriding the constructor and argument parsing

Disabling typechecking

You can use typecheck: false to generate an argument parser that doesn't check fields' types. This is useful to retain type information but disable checking if that's needed. The performance difference is likely to not be too significant, so that likely wouldn't be enough of a reason, unless too many advanced typesystem features are used.

Custom constructor

You can use construct: default-constructor => (..args) => value to override the default constructor for your custom type. You should use construct: rather than creating a wrapper function to ensure that data retrieval functions, such as e.data(func), still work.

Custom argument parsing

You can use parse-args: (default arg parser, fields: dictionary, typecheck: bool) => (args, include-required: bool) => (true, dictionary with fields) or (false, error message) to override the built-in argument parser. This is used both for the constructor and for set rules.

Here, args is an arguments and include-required: true indicates the function is being called in the constructor, so required fields must be parsed and enforced.

However, include-required: false indicates a call in set rules, so required fields must not be parsed and forbidden.

In addition, the default arg parser function can be used as a base for the function's implementation, of signature (arguments, include-required: bool) => (true, fields) or (false, error).

Warning

Note that the custom args parsing function should not panic on invalid input, but rather return (false, "error message") in that case.

This is consistent with the default arg parser function.

Argument sink

Here's how you'd use this to implement a positional argument sink:

#let sunk = e.element.declare(
  "sunk",
  display: it => {
    (it.run)(it)
  },
  fields: (
    field("values", e.types.array(stroke), required: true),
    field("run", function, required: true, named: true),
    field("color", color, default: red),
    field("inner", content, default: [Hello!]),
  ),
  parse-args: (default-parser, fields: none, typecheck: none) => (args, include-required: false) => {
    let args = if include-required {
      // Convert positional arguments into a single 'values' argument
      let values = args.pos()
      arguments(values, ..args.named())
    } else if args.pos() == () {
      args
    } else {
      // Return errors the correct way
      return (false, "element 'sunk': unexpected positional arguments\n  hint: these can only be passed to the constructor")
    }

    default-parser(args, include-required: include-required)
  },
  prefix: ""
)

// Use 'run: func' as an example to test and ensure we received the correct fields
#sunk(
  5pt, 10pt, black, 5pt + black,
  run: it => assert.eq(it.values, (5pt, 10pt, black, 5pt + black).map(stroke))
)