This document describes src/Plato.Interpreter: an evaluator over the compiler's Typed IR (TIR)
that runs any bodied Plato function, and every Law_* function, in the compiler's own process,
with no C# emitted or built. It is aimed at people working on the interpreter, on the law runner,
or on the verification tooling that builds on it (sandbox/step-brep-mesh/SPEC-EXECUTION.md in
the bim-open-toolkit repository, whose plato verify is the engine's first client beyond laws).
For the pipeline the interpreter sits on, see compiler-pipeline.md. For
measurements (how long a run takes, how many laws pass on a given day) see the dated report
reports/interpreter-turnaround-2026-10-17.md;
this document states design only, as documentation-conventions.md
asks.
Before the interpreter, every change to a Plato body meant compile, generate C#, build the
generated project, then run the laws through the reflection runner in
tests/conformance/Plato.ForwardConformanceTests. That loop was slow enough that algorithm bodies
were drafted in C# first and ported later. Plato is pure, its sums are closed, and its laws are
Boolean functions over values the same program can construct, so an evaluator over the TIR is
small, and it can run the laws in the same process as the front end. The interpreter is also the
execution engine the specification tooling needs: a bounded quantifier, an invariant, or a theorem
test line is an expression evaluated against an environment, and that is the one operation the
interpreter exposes.
source (.plato) ─parse─▶ AST ─bind─▶ Symbol graph ─normalize, constrain, solve, elaborate─▶ TIR
│
┌───────────────────────────────────────────────┤
▼ ▼
Interpreter (this document) Monomorphizer ─▶ writers
The interpreter consumes the elaborated TIR of one Compilation (Interpreter.cs). It does not
run the monomorphizer: generic bodies run unspecialized, and the decision the monomorphizer makes
statically, which concrete function a call through an interface reaches, is made at run time from
the receiver's value (see Dispatch).
Elaboration is lazy and per function. Interpreter.GetTir(FunctionDef) checks and elaborates a
body the first time it is reached and caches the result, so a law over a small corner of a
library costs only the functions it calls, and a program with thousands of functions does not
pay for all of them before the first law runs. ElaboratedCount reports how many bodies a run
touched; the plato run summary line prints it.
Interpreter values (Values.cs) are ordinary .NET objects. Every value carries enough type for
dispatch to match it against a parameter at run time, which is what lets generic bodies run
without specialization.
| Plato | Representation |
|---|---|
Number |
double, under every profile (see Number profiles) |
Integer |
long |
Boolean, Character, String |
bool, char, string |
| a product type | PlatoRecord: the TypeExpression (with type arguments when known) and one slot per declared field, in declaration order |
| a sum type | PlatoSum: the type, the SumCaseDef it carries, and the case's payload |
Array<T> |
PlatoArray: an immutable list plus the element type |
List<T>, Buffer<T> |
PlatoBuilder: the two affine builders; a mutator returns the same builder, Freeze hands the storage to a PlatoArray and invalidates the builder, as PlatoList and PlatoBuffer do in the C# runtime |
| a function value | PlatoFunction: a lambda closed over the environment it was created in, or a host delegate |
| a type used as a value | TypeToken: the receiver of a _-parameter function such as Number.Pi or BrepModel.Witness_UnitBox |
Arrays are eager: every element is materialized when the array is built, so MapRange over a
million elements performs a million calls up front. The C# runtime's array is lazy. The law
runner's inputs are small, and eagerness is the simpler choice; a client that needs laziness
would add it to PlatoArray rather than to the evaluator.
ValueEquality gives structural equality and hashing, field by field, mirroring what the C#
backend generates for a struct: a Number compares with double.Equals, so NaN equals NaN and
0.0 equals -0.0. ValueFormat renders a value for a report (round-trip numbers, nested
records as Type(f1, f2), arrays as [a, b]), truncated past a size budget so a failing witness
over a large model stays readable.
A NumberProfile (src/Plato.Compiler/Types/NumberProfile.cs, the per-compilation profile of
TKT-315) fixes the width of Number and Integer. The interpreter takes one in its constructor
and routes every literal and every host intrinsic through Numbers (Numbers.cs):
Numberis always stored as adouble. Undercpu64it is binary64 arithmetic. Undergpu32each result of+,-,*,/andsqrtis rounded to binary32 after the binary64 operation, which gives the correctly rounded binary32 result, so the stored doubles are exactly the floats agpu32C# build would hold. Transcendental functions go throughMathFundergpu32andMathundercpu64, as the C# runtime'sNumberMathalias does.Integeris along. Undergpu32it wraps to 32 bits after every operation, as the C# runtime'sintdoes; undercpu64it is 64-bit storage, exact within 2^53 when converted toNumber.- Hashes,
MinValue,MaxValue,EpsilonandPifollow the profile's width.
A literal typed Number by the checker is a double; one typed Integer is a long. The
profile is chosen once per Interpreter instance; running the same compilation under both
profiles is two instances over the same Compilation.
Interpreter.Evaluate(TirNode, Environment) is a case per TIR node class (the node classes are
in src/Plato.Compiler/Checking/Tir.cs). Expressions evaluate to a value; TirBlock, TirIf,
TirLoop and TirReturn are executed as statements, and a block body that ends without a
return is a fault. match needs no case of its own: the elaborator has already lowered it to a
chain of conditionals over the sum's predicate and projection calls (see Dispatch, syntactic
calls).
An Environment (Environment.cs) binds parameters and var locals by their definition
symbol, has a parent for nested scopes, and records the concrete type Self denotes in the
activation (the receiver's run-time type when the first parameter is abstract, else its declared
type). A lambda captures a flat snapshot of the bindings visible where it was created: the
value-at-creation semantics the C# writer reproduces by hoisting captured locals into copies.
Activations deeper than MaxDepth are reported as a fault rather than overflowing the host
stack; plato run evaluates on a thread with a large stack so that deep but finite Plato
recursion fits.
Dispatch (Dispatch.cs) decides which function a call reaches once the argument values are
known. There are four routes, tried in this order for a TirCall:
- Static callee. The elaborator resolved the call to one
FunctionDef. A field reads the slot; a constructor builds the record; a cast returns its argument; a sum factory builds the case; the sum's generated equality compares structurally; a library function with a body runs it; a bodiless one goes to the host table. - Interface declaration, re-dispatched. When the static callee is an interface member, the call is resolved again by the receiver's run-time type: a field of that type with the member's name, then a library function over the concrete type, then one over an interface the type implements, then the shape-derived members below, then a default body on the interface. This is the decision the monomorphizer makes statically once the receiver is concrete.
- Syntactic call. A call the elaborator left without a callee: the predicates, projections
and factories the
matchlowering and the qualified case syntax produce (IsCircle(c),Circle_Radius(c),BrepCurve.Circle(f, r)), a field read, or a name resolved against the argument values by the checker's own tiers (exact, generic, interface, conversion). - Shape-derived members. The members the C# writer synthesizes from a struct's declared
shape rather than from Plato source: structural
Equals,NotEqualsandHash;CountandAtover the fields, or over a single indexable field;Components,NumComponents,FromComponents;FieldNames,FieldValues,TypeName.
Overload choice (ResolveByValues) scores each candidate by the checker's tiers, summed over
the arguments; ties go to an implementation over an interface declaration, then to a bodied
function, then to declaration order. A bare group name in value position (One, Zero,
Epsilon) resolves to the nullary function returning the expected type, or to the
_-receiver function of that type.
The checker admits a fixed set of conversions at a boundary, and the elaborator records most of
them as TirCoerce nodes. The interpreter applies the same set, in CoerceTo, at every place
the TIR does not spell one out: a function's return, a constructor's fields, and the arguments of
an overload chosen at run time.
IntegertoNumber.- A tuple to the struct of the same shape (
(x, y)returned where aPoint2Dis declared). - A one-field struct to its field's type (the compiler's single-field cast,
Number(a: Angle)). - A user conversion: a one-parameter function named after the target type that takes the source type.
A TirCoerce with a named conversion function applies that function when its parameter accepts
the value, else the structural rules above; a value that fits neither is a fault, because the
checker promised a conversion.
The stdlib's bodiless primitives (stdlib/foundation/intrinsics.library.plato) are supplied by
HostIntrinsics (HostIntrinsics.cs): a table keyed by the name and the declared parameter
type names, Add(Number, Number), Count(Array), mirroring the C# runtime in
src/Plato.Intrinsics operation for operation, with the profile deciding the width. The array
kernel is the five functions Count, At, MapRange, Reduce and FlatMap; everything else
over arrays is Plato.
A bodiless function with no table entry is a host obligation, raised as
HostObligationException with the function's key. The STEP design's deferred algorithms are
declared this way on purpose (TASKS.md in the sandbox lists which chunk gives each one a body),
so the law runner reports an obligation by name instead of counting the law as failed, and never
substitutes a default value. An interface member no implementation satisfies is reported the
same way, with the receiver's type.
Faults inside a host operation (a bad index, a division by zero, an invalid cast) become
EvaluationExceptions that carry the chain of Plato functions active at the time, outermost
last, so a report can say where a law faulted.
LawRunner (LawRunner.cs) is a thin driver over the interpreter; the conventions live here,
not in the evaluator, so a different convention is a different driver over the same engine.
- A law is a bodied function named
Law_*returningBoolean. Its parameters are its domain. - A witness is
Witness_X(_: T): T, the sandbox's static-receiver form, or a nullaryWitness_X(): T. Every witness returning a type is evaluated once and cached; a witness that faults is left out, and the laws over it report the fault. - A generator is a bodied
Generate_X(seed: Integer): T, orGenerate_X(_: T, seed: Integer): Tin the static-receiver form, whereTis a declared type (not a primitive such asNumber,IntegerorArray<T>); the return type is matched whole, arguments included. A type with a generator is never built field by field: wherever the runner needs aT, as a law argument, as a field of another generated value, or as an implementer of an interface, it calls one of the type's generators, chosen at random, with a seed drawn uniformly from 0 toValueGenerator.GeneratorSeedsless one, and its witnesses still join the draw as preferred values. Such a type has no finite domain, so it is sampled, not exhausted. This is how a library says a type's constructors keep an invariant the fields alone do not, for example aLengthTolerancewhoseValueis positive: without a generator the runner draws 0 or -0.5. The interpreter evaluates each generator once per seed, under a lock (a compiled backend draws from several threads), so every backend and the differential run receive the same values, converted. A generator that faults makes the law an error naming the generator and the seed (TKT-446). - A sealed type (
sealed by F, G, docs/SEMANTICS.md section 2) is never built field by field either. With a generator it is drawn from the generator; otherwise from one of the named functions that has a body, chosen at random and called on drawn arguments, so a constructor that takes the type itself (WorseOf(a: Check, b: Check)) composes drawn values untilValueGenerator.MaxDepth. Its witnesses join the draw. A sealed type none of whose named functions has a body, or can have its arguments drawn, is not generated, and a law over it runs on its witnesses or reports why it cannot. A named function that faults on drawn arguments makes the law an error naming it (TKT-444). - A measure is a companion
Measure_<law>with the law's parameter list. When a law fails or faults, the runner evaluates the measure on the failing witness and prints it beside the witness, so a report names how far off the result was, not only that it was. - A premise is a companion
Premise_<law>with the law's parameter list, returningBoolean: the condition under which the law says something, for a guarded law such asp ? q : true. The runner evaluates it on every case the law passes; the pass line reads(25 cases, premise held on 7), and a law whose premise held on no case is vacuous, a failing verdict (vacuousin a verdict file, exit code 1), because it tested nothing. - A
_parameter ranges over the type token of its declared type. - Finite domains are exhausted:
Boolean, a sum of payload-free cases, a zero-field type (aprimitivedeclaration such asNumber,String,Character,Array<T>orFunctionN<...>is not one: its values are generated at the top level of a law as in a field, fixed in 1a3298fa and guarded byValueDomainTests), andInteger, which ranges over a closed interval wide enough to reach every witness a selector such asWitnessAt(k)can pick plus an out-of-range value. A law whose parameters are all finite, with a product no larger thanMaxExhaustive, runs every case. - Otherwise the law is sampled,
Trialstimes, each draw taking a witness or a generated value with equal chance when both exist.ValueGeneratorfollows the C# conformance runner's policy (aNumberis one of ten special values three times in ten and otherwise uniform in [-10, 10]; anIntegeris uniform in [-8, 8]; a struct is built field by field to a depth limit) and goes further where that runner stops: an array of a few elements, a sum case chosen at random, a zero-field type as its only value. An instance of a generic type (Maybe<EdgeIndex>,Tuple2<Number, Integer>) is drawn with its type arguments substituted into its field and case types, and each type argument by its own rule (its generator, or its sealed constructors). Every draw comes from oneRandomseeded from a stable hash of the law's name, so a run is reproducible and a failing case can be found again. A type with a generator is drawn from it instead (above). - A parameter typed by an interface ranges over every concrete type implementing it, as the C# runner's monomorphization does: the witnesses of each implementer plus generated values of the implementers that can be generated.
- A law no value can be built for is skipped with the reason (a type variable, a function parameter, a builder, nesting deeper than the generator's limit with no witness).
The result per law is one of pass, fail (the first failing witness and the measure), error
(a fault, with the Plato call chain), obligation (the bodiless function reached), or skipped
(the reason). The generator's sampling policy is deliberately the same as the C# runner's, so
that a law that fails on generated input in one engine fails in the other.
Plato.CLI run <folder>... (--laws | --call=<Name>) [--recursive] [--exclude=<file>]
[--profile=cpu64|gpu32] [--backend=interpreter|csharp]
[--filter=<text>[,<text>...]] [--laws-from=<file>]
[--report=<file>] [--baseline=<file>]
[--trials=N] [--seed=N] [--budget=<seconds>] [--abandon=<seconds>] [--parallel=N]
RunCommand.cs compiles the roots as one program (top-level files, or every subfolder with
--recursive; *.spec.plato is always skipped, because the specification syntax is not yet in
the grammar) and runs it in this process. --laws prints one line per law and a summary line
with the counts, the number of bodies elaborated, and the compile and law times separately.
--call=<Name> invokes a nullary function, or a _-receiver one as Type.Name, and prints its
value. --filter takes a comma-separated list and keeps the laws whose full name
(Owner.Law_Name) contains any entry, ignoring case, so an entry names a law, its class, or both;
--laws-from=<file> adds the entries of a file, one per line, blank lines and # comments
skipped. Both may repeat, and the run covers the union. An entry that selects no law is reported
on stderr, so a misspelt name in a list does not pass silently. --trials and --seed set the
sampling. --budget is the seconds one law may take over all its cases
(LawRunOptions.DefaultBudgetSeconds); a law over it is an error whose message gives the time it
took, and --budget=0 lifts the limit. The exit code is 1 on a parse or compile failure and when
any law fails or errors; skipped laws and host obligations are listed but do not fail the run.
The summary line gives the wall time and the law times summed, and the fifteen slowest laws and
witnesses are listed before it.
--backend chooses where the laws evaluate (LawBackends in src/Plato.Laws). interpreter,
the default, is this document's evaluator: one law at a time, stopped at its budget by the
interpreter's deadline. csharp emits the program with the C# writer in extension style,
compiles it in process with Roslyn together with the embedded copy of src/Plato.Intrinsics
(CSharpProgram), and calls each law as the method the writer made of it (CSharpBackend): a
library law is a static method of its library's class, a law whose first parameter is an
interface is an instance method on each implementing struct, a _ receiver is dropped. Laws
then run on every core at once, one thread per law (--parallel=N bounds it), and witnesses are
built first, once per type, so a law's time is its own. A compiled law cannot be interrupted, so
it runs to the end and its time is judged against the budget; past --abandon seconds its thread
is left behind and the law is an error. Both backends build their cases from the same domains
and the same seeded draws (ILawBackend in LawRunner.cs: the runner supplies the values, the
backend evaluates), so the verdict of a law is the same on either unless the writer, the runtime
or the interpreter disagree, which the differential test (DifferentialRun, run by
tests/Plato.Interpreter.Tests/DifferentialTests.cs) exists to catch.
--report=<file> writes a verdict file: one tab-separated line per law, sorted by name, holding
the full name, the verdict (pass, fail, error, obligation, skipped), the milliseconds
the law took, and the first failing witness, the fault or the reason, flattened to one line. A
file written before the time column existed has three columns and still parses and compares.
Two overloads with one name carry their parameter types. A committed verdict file records which laws held at a commit, and
two of them diff line by line. --baseline=<file> compares the run with a verdict file and
prints every law whose verdict changed (regressed, changed, new, or, in a run without
filters, removed); only verdicts are compared, never the witness text. With --baseline the
exit code says only whether something regressed: 1 when a law the baseline records as passing
fails, errors, is skipped, becomes an obligation, or (in an unfiltered run) is gone; laws that
already failed in the baseline do not fail the check. The baseline is read before the run, so
--report and --baseline may name the same file to check and then advance it.
LawReport and LawVerdict in LawRunner.cs hold the format and the comparison.
plato run compiles the whole program before its first law, every time; on a large program
that compilation is nearly all of a filtered run. WarmLawRunner (src/Plato.Navigation)
keeps it between runs for a long-lived host, and the plato_laws tool of the navigation MCP
server is that host. With backend=csharp it also keeps the compiled C# program, one per source
generation and Number profile, recompiling only when a file changes; the emit and Roslyn times
are reported beside the others. Its arguments are the CLI's flags (folders, recursive, exclude,
filter, lawsFrom, profile, trials, seed, budget, report, baseline), and it
chooses files through the same LawProgram.SourceFiles, so the two reach the same verdicts.
The runner keeps one program (WarmProgram) per choice of folders, recursive flag and excluded
names, up to a bound (DefaultMaxPrograms, the server's --law-programs=<n>), dropping the least
recently used. Callers on different folder sets, such as tracks each adding its own staging
folder to the library, therefore stay warm instead of recompiling each other's program. A
program's compilation serves every Number profile; each profile has its own interpreter. Runs of
one program are serialized and runs of different programs proceed together. One ParseCache
serves all programs, so the library files they share are parsed once.
Each run reads its program's files and compares their contents with that program's last run:
- Nothing changed: the compilation and the interpreter are reused, with every body already elaborated, so the run costs the laws alone.
- A file changed: only that file is parsed again, and the program is compiled once more, because the compiler has no incremental mode. The interpreter is rebuilt on the new compilation. Other programs are untouched.
- A file does not parse: the run returns one "Parsing failed: : " error
per parse error and runs no law, as
plato rundoes. The parser places some errors, a missing semicolon among them, at the start of the file, so the message names the file but not the line.
A run can also type-check (TypeCheckScope, the tool's types argument). ChangedFiles, the
tool's default, checks only the functions declared in files whose text changed since the last
check and keeps every other file's last errors, so an edit costs a fraction of a second of
checking; All checks every function once per compilation, which also catches a caller in an
unchanged file that the edit broke. Type errors come back with file, line, code and message,
the same location rule plato_check uses.
Plato.Navigation.CLI lawbench measures the cases on any set of folders: cold, unchanged, and
after three edits to one file (--types or --all-types adds the type check, --phases the
compiler's timed phase log). Point --touch at a copy, since the bench rewrites that file and
restores it.
Measured on 2026-10-18 over the STEP library (the three stdlib tiers plus a copy of
sandbox/step-brep-mesh, recursive, the three brep.* stdlib files excluded, cpu64, one law
selected; 250 files, about 14,400 functions), on a machine shared with other sessions:
| Run | Time | Where it goes |
|---|---|---|
plato run --laws --filter=<one law>, cold CLI |
about 33 s | 32.5 s compiling, 0.3 s on the law |
Warm, first run (lawbench, 15:55) |
19.8 s | 9 to 10 s parsing, 9 to 12 s compiling |
Warm, no file changed (lawbench and plato_laws) |
0.4 s | the law alone |
Warm, one file changed, writer tables built in the constructor (lawbench, 15:55 to 15:59) |
8.6 to 12.6 s | 0.1 s parsing, 7.9 to 11.8 s compiling |
Warm, one file changed, writer tables built on first use (lawbench, 16:00) |
5.7 s | 0.1 s parsing, 5.0 s compiling, 0.6 s on the law |
plato_laws round trip after a one-body edit, with the changed-file type check (18:05) |
4.8 to 6.0 s (six runs: 4.8, 4.9, 4.9, 5.4, 5.5, 6.0) | 4.1 to 5.5 s compiling, 0.2 to 0.3 s type-checking the file, 0.3 to 0.4 s on the law |
Between 16:05 and 17:55 the machine ran ten to twenty-five times slower under outside load, and
readings from then were discarded; one 18:05 round trip of 24 s, whose compile alone took 16.5 s,
was taken during such a spike and is left out of the range above. The compiler's own phase log
(lawbench --phases) shows where a recompile went before the writer tables moved: resolution
about 0.7 s, reifying library functions about 3.5 s, and building the code writers'
LibraryFunctions and ConcreteTypes tables about 5.3 s. The interpreter reads neither table,
so Compilation builds them on first use; the generated C# of the foundation tier is
byte-identical either way. A check of every function took 6 to 21 s on this program in these
runs, which is why ChangedFiles is the default. What remains of an edit is whole-program
resolution and reification, so getting reliably under 5 s needs an incremental compiler, not more
from this runner.
Two callers alternating between two staging folders (TKT-344), measured at 23:26 on 2026-10-18
through plato_laws on a scratch server: the library above plus a copy of
sandbox/step-mesh-staging/genus as one program and plus poles as the other, gpu32, one law,
the changed-file type check. The first call of each program compiled (21.7 s, then 6.9 s, the
second reusing the parsed library). Afterwards each program answered 0.0 to 0.2 s without
recompiling while the other was being edited, and 4.0 to 4.6 s after a one-body edit to its own
staging folder (fourteen edits, alternating; 3.7 to 4.3 s of it compiling, 0.2 s type-checking).
Memory, as managed heap after a full collection: 510 MB with one program kept (the shared parse
trees included), about 80 MB more for each further program over the same library, so the default
bound of eight costs about 1.1 GB. The server's private bytes rose and fell between 1.0 and 1.7 GB
over the fourteen recompiles without growing.
The STEP design folder compiles with the three stdlib tiers plus the sandbox under --recursive,
excluding the three brep.*.plato stdlib files the sandbox redeclares; the exact invocation is in
the dated report.
Three entry points, on Interpreter:
Call(string name, params object[] args)chooses the overload as the checker would from the values' types and applies it.Invoke(FunctionDef f, params object[] args)applies one known function: its body, the host intrinsic it names, or the value a constructor, field, cast or sum factory denotes.Evaluate(TirNode node, Environment env)evaluates one TIR expression against an environment.
Evaluate is the operation SPEC-EXECUTION.md's plato verify builds on: a bounded quantifier
binds its variable in an Environment and evaluates its body; an invariant is an expression over
the fields bound in an environment; a theorem's test line is a law with its own domain. No second
engine is needed for any of them.
These are known and deliberate. The differential test the ticket asks for (the stdlib's laws run
both ways, compared law by law under both profiles) is the check that no other divergence has
crept in. For a library of your own, plato run --laws --differential (the warm server's
backend=differential) is the same check over its laws and witnesses: every Witness_* is
built in both engines and compared with Plato's structural equality, and every law and its
Measure_ and Premise_ companions run on the same witnesses in both. A law whose answers
differ is reported differs with the case and both answers, a witness built differently is
listed and dropped. Without an explicit baseline, the exit code is 1 on any disagreement or
failing, erroring, or vacuous law, including when both engines agree on that verdict. Skipped
laws and host obligations retain the ordinary run's non-failing treatment. The explicit
--baseline mode still reports regressions alone. Calls run one at a time, and
the interpreter's --budget keeps the slow interpreter arm to the laws that are quick: a law it
stops is a budget error, never a mismatch (TKT-438). Only the calls the runner makes are
compared, not the calls inside them, so a wrong helper shows up through the laws that use it.
- Arrays are eager here and lazy in the C# runtime. The observable difference is cost, and the point at which a faulting element function faults.
- Hash seeds. The C# backend combines field hashes with
HashCode.Combine, whose seed is per process, so no hash value is comparable across runs in either engine; the interpreter's hash is stable within a process and consistent with its equality. - String ordering follows the C# runtime:
string.Comparewith the current culture, which is what the generated code calls.
What the interpreter does not do yet, found by running the STEP design folder and the stdlib laws; the dated report has the counts and the exact messages.
- A static callee is applied as resolved, and only an interface declaration is re-dispatched.
When the checker resolved a call on a type-variable-typed expression to a generic library
function whose parameter is an interface (
Add(x: IOriginBased<$D>, delta: $D)chosen forx.Offset + delta, whose operands are vectors), the interpreter runs that body with a receiver that does not implement the interface, and the next member call on it is reported as a host obligation (IOriginBased.Offset on Vector3D) that no body could discharge. The fix is to re-resolve a static callee whose declared parameters the run-time arguments do not fit, by the same tiers the syntactic route uses. Type.Namein value position, whereName's first parameter is a value rather than_(Point2D.OriginwithOrigin(x: IOriginBased<$D>)), passes the type token as the value and faults at the first field read.--excludematches a file name anywhere under the roots; there is no way to exclude a folder.