LEVIATHAN v962456e · 962456eee1

Metaprogramming

Reification errors

The compile errors a reified lambda can produce, what each means, and how to fix it.

since 0.1.0-alpha.1linuxwindowswasm

Description

A lambda that cannot be turned into a tree is a compile error, never a silent fallback to a closure. Each error points at the offending expression. It is followed by a note that marks the lambda (in this lambda reified to expr::Expr<F>) and, for the errors about the body, by a note listing the method calls that are allowed:

reifiable calls: string.like/1, string.ilike/1, string.startsWith/1, string.endsWith/1, string.contains/1, Array.contains/1

There are three kinds of error.

A value where a lambda literal is needed. only a lambda literal can be reified to expr::Expr<F> means that a variable holding a lambda reached a position typed expr::Expr<F>. Values cannot be reified, because the tree is built from the source of a literal. Write the lambda at the position where it is used.

A construct outside the reifiable subset. cannot reify <construct>: outside the … reifiable subset is reported with one of these constructs:

<construct> what caused it what to do
non-whitelisted call 'name' a method call that is not one of the six allowed calls compute the value outside the lambda and capture it
block body (u) => { … } write a single expression
conditional expression a ? b : c use && and ||
indexing a[i] capture the element before the lambda
'is' expression, 'match' expression a type test or match not available in a reified lambda
nested lambda a lambda inside the lambda not available
await an await expression await before, and capture the result
assignment outside a set-shaped lambda x = … anywhere except as the whole body of an (E) => int lambda not available
capture of a non-value type a captured object, or an array not used with contains capture the individual string, int, float or bool values

String interpolation has no message of its own. An interpolated string expands to calls to toString and to +, so using one in a reified lambda reports non-whitelisted call 'toString'.

An ambiguous call. ambiguous lambda argument: matches both a function parameter and an expr::Expr parameter; extract a typed local to select is reported when two overloads differ only by taking a function or an expr::Expr, and the call passes a lambda literal. Rename one overload, or assign the lambda to a local of the exact type first.

class User { string name; int age; }
User other = User();

// error: only a lambda literal can be reified to expr::Expr<F>
(User) => bool held = (u) => u.age > 1;
expr::Expr<(User) => bool> a = held;

// error: cannot reify non-whitelisted call 'toUpper': outside the … reifiable subset
expr::Expr<(User) => bool> b = (u) => u.name.toUpper() == "A";

// error: cannot reify block body: outside the … reifiable subset
expr::Expr<(User) => bool> c = (u) => { return u.age > 1; };

// error: cannot reify non-whitelisted call 'toString': outside the … reifiable subset
expr::Expr<(User) => bool> d = (u) => "${u.name}" == "x";

// error: cannot reify capture of a non-value type: outside the … reifiable subset
expr::Expr<(User) => bool> e = (u) => other == u;
not run — shows a compile error

Rules

  • Every reification error is reported at compile time. A program with one never starts.
  • The note that lists the allowed calls is generated from the same table the compiler uses to accept calls, so it always shows the current list.
  • Fixing an error means rewriting the lambda into the subset described on the subset page, or moving the unsupported part outside the lambda and capturing its result.

Examples

The repair for a rejected method call is to evaluate it outside the lambda:

Computing outside the lambda

class User {
    string name;
    new User(string n) { name = n; }
}

string wanted = "ADA".toLower();
expr::Expr<(User) => bool> e = (u) => u.name == wanted;
console.writeln(e.fn(User("ada")));
console.writeln(e.binds.length());
true
1

See also

  • Expression reification — lambdas as data — A lambda literal in a position typed expr::Expr<F> compiles to an ordinary closure plus a walkable tree of its checked body, which is what query builders translate to other languages.
  • Where a lambda literal is reified — A lambda literal becomes an expr::Expr<F> in any position whose expected type is expr::Expr<F>: a call argument, a declared variable, a return value or a field initializer.
  • The reifiable subset — The expressions that may appear in a reified lambda and the tree node each one becomes.
  • Method calls in a reified lambda, and like / ilike — The six method calls that may appear in a reified lambda, and the exact matching rules of string.like and string.ilike.