Skip to content

Add gradual-typing elaboration to Laurel resolution#1422

Open
julesmt wants to merge 5 commits into
reviewed-kbd-will-merge-to-mainfrom
native-types/laurel
Open

Add gradual-typing elaboration to Laurel resolution#1422
julesmt wants to merge 5 commits into
reviewed-kbd-will-merge-to-mainfrom
native-types/laurel

Conversation

@julesmt

@julesmt julesmt commented Jun 26, 2026

Copy link
Copy Markdown
Member

Adds an optional GradualConfig so resolution keeps native operators on native operands and routes Any operands through prelude operators, inserting box/unbox/toBool casts. Inert without a config; strict typing
unchanged. Foundation for the stacked native typed-Python PR.

Massart added 5 commits June 25, 2026 12:22
Synth.primitiveOp inlined two match arms -- one for the arithmetic operators,
one for the boolean / comparison / equality / concatenation families. Extract
them into standalone nativeArith and nativeOther helpers (with an isArithmeticOp
predicate and a joinAll LUB fold), and have Synth.primitiveOp resolve its
operands once and dispatch to them.

Pure refactor, no behavior change. Gives the native typing rules a single home
so a later commit can reuse them as the fallback for gradual elaboration.
For datatype constructors, getCallInfo returned an empty parameter-type list, so
constructor arguments were never checked against their declared types. Return
ctor.args.map (.type) so a constructor call is type-checked like any other call.

No behavior change for existing programs (their arguments already matched); this
lets a later commit coerce constructor arguments under gradual typing.
Add an optional GradualConfig (a frontend's dynamic type, its boxing
constructors, checked downcast accessors, per-operator prelude functions, and
which procedures to elaborate). A gradualActive flag, set only inside a
frontend's own procedures, gates a single dispatch in Synth.primitiveOp: when
active, route operands through synthArith / synthOther, which keep a native
operator when the operands are native and switch to the Any-prelude operator
(inserting box / unbox / Any_to_bool coercions at boundaries), falling back to
the native nativeArith / nativeOther rules; when inactive the native rules apply
unchanged. Inert until a frontend supplies a config.
Add gradualConfig to LaurelTranslateOptions and pass it from
LaurelCompilationPipeline into resolve by named argument. Defaults to none, so
pipelines that supply no config are unaffected.
filterPrelude runs before elaboration, so the prelude calls it may insert are
not yet referenced by the user program. Add extraSeeds (retained unconditionally)
and a generic opSeed (Operation -> Option String) so an operator's prelude
function is retained only when that operator appears. Inert without seeds.
@julesmt julesmt requested a review from a team as a code owner June 26, 2026 18:22
@julesmt julesmt requested a review from leo-leesco June 26, 2026 18:23

@leo-leesco leo-leesco left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great PR ! This goes really helps moving forward to lower Dyn type into Laurel
I wrote a few design considerations that we can discuss and asked for some modifications

Comment on lines +144 to +147
/-- The dynamic type whose values are boxed (`TCore "Any"` for Python). -/
dynamic : HighType
/-- Whether a type is the dynamic type (may have several spellings). -/
isDynamic : HighType → Bool

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Design consideration : instead of threading this Dyn type in the resolution, can we imagine referencing it throughout the program?
In other words, do we expect Dyn to be source-language dependent ? This looks like a dependency inversion to me, if we were to add a new type to express the Dyn type, we could probably defer translating the source-language Dyn to Laurel's Dyn to the corresponding frontend

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure, like a canonical HighType.Dyn constructor in LaurelAST.lean?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe another PR no ?

Comment thread Strata/Languages/Laurel/Resolution.lean
Comment on lines +758 to +763
/-- LUB of the operand types, or `none` if inconsistent. -/
private def joinAll (ctx : TypeLattice) (argTypes : List HighTypeMd)
(source : Option FileRange) : Option HighTypeMd :=
argTypes.foldl
(fun acc t => match acc with | some l => join ctx l t | none => none)
(some { val := .Unknown, source := source })

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just a warning : this is not yet an actual LUB (please update doc) as it does not walk any inheritance tree

join simply erases any Unknown if tried to merge with another static type. Can you look at join and see if it needs updating using your GradualConfig ?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch

Comment thread Strata/Languages/Laurel/Resolution.lean
Comment on lines +780 to +792
/-- A `MultiValuedExpr` operand (a multi-output call used in value position) is
an internal pseudo-type with no Core lowering, so it must never reach an
operator slot — letting it through crashes a later pass as a `StrataBug`.
Emit the position-oriented diagnostic and return `true` so the caller
short-circuits to the operator's result type, suppressing cascades. -/
private def reportMultiValued (a : StmtExprMd) (aTy : HighTypeMd) : ResolveM Bool := do
match aTy.val with
| .MultiValuedExpr _ =>
let diag := diagnosticFromSource a.source
"multi-output call cannot be used as a value here; it returns multiple values. Unpack it into separate variables first"
modify fun s => { s with errors := s.errors.push diag }
pure true
| _ => pure false

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might be wrong, but I seem to remember the typechecker was already making the same kind of check ; if not we should move this helper function with the rest of the typechecker

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll ask kiro to check if you have something similar to extract a helper !

-- and return `true` so the caller short-circuits to the operator's natural
-- result type, suppressing the per-family check (and its cascading error)
-- on that operand.
let reportMultiValued (a : StmtExprMd) (aTy : HighTypeMd) : ResolveM Bool := do

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we can discuss the location of this ? If it makes sense to move it, that's perfectly okay

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah let's discuss it monday

Comment on lines +27 to +29
/-- Substring containment. -/
private def containsSubstr (haystack needle : String) : Bool :=
(haystack.splitOn needle).length > 1

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[out of scope of this PR] : considering we have a similar helper function used to match annotations in tests, maybe we could factor both of them out ?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

true

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants