Skip to content

Rewrite of Python FE to separate out Python-specific resolution, threading of effects, and coercion insertion with Laurel resolver#1415

Draft
ssomayyajula wants to merge 10 commits into
strata-org:reviewed-kbd-will-merge-to-mainfrom
ssomayyajula:ssomayyajula/incremental-hybrid-experiment-v4
Draft

Rewrite of Python FE to separate out Python-specific resolution, threading of effects, and coercion insertion with Laurel resolver#1415
ssomayyajula wants to merge 10 commits into
strata-org:reviewed-kbd-will-merge-to-mainfrom
ssomayyajula:ssomayyajula/incremental-hybrid-experiment-v4

Conversation

@ssomayyajula

Copy link
Copy Markdown
Contributor

TODO

…lusive / 14 internal

Verified-green snapshot BEFORE the proof-relevant-coercion change.
- Build: lake build pyAnalyzeV2 strata = 515 jobs, exit 0
- Unit suite (--solver z3): 159 success / 4 fail / 43 inconclusive / 14 internal / 0 crash
- src/Strata (v2 spec) untouched

Recovery point in case the resolver/elaborator coercion refactor regresses.
…t Coercion verdict

LaurelAST.lean: add Coercion (refl|inject|project|upcast) + coerce judgment that
returns the abstract verdict, reusing the existing unfold/gradualTypes/ancestors.
isConsistentSubtype := (coerce …).isSome — ONE judgment, boolean derived from it,
so the yes/no answer and any inserted coercion can never disagree. TypeLattice gains
realizeCoercion + toBool caller hooks (default none = identity, native Laurel).

GENERIC: the verdict names the KIND of coercion, never a runtime function; the
caller (Python frontend) realizes it. No realizer wired yet -> behaviorally neutral.

Build green. Unit suite (--solver z3): 159/4/43/14 = EXACTLY the baseline (no change),
confirming the boolean is provably identical to the old isConsistent ∨ isSubtype.
…r effects-only

Wire the Python realizer (pythonRealizeCoercion/pythonToBool) onto TypeLattice via
LaurelTranslateOptions; flip elaborator applySubtype to identity (the two coercers are
mutually exclusive). coerceTo realizes the coerce verdict at the subsumption chokepoint,
assignment RHS (both gaps), datatype-ctor args (getCallInfo), and preconditions (vs TBool).

KEY FIX: isDynamicBoxable must recognize Any as BOTH .TCore "Any" (the erased/runtime
form, the common case) AND gradual .UserDefined "Any". Unknown/other-TCore stay gradual
wildcards (refl, no coercion) so prelude/heap-plumbing code isn't spuriously wrapped.
Composite↔Any typed via re_Match (flattens to Composite); from_Composite is bare.

Unit suite (--solver z3): 151 success / 4 fail / 41 inconclusive / 24 internal / 0 crash.
Up from the 212-internal mid-refactor low; 24 remaining are coverage gaps still being
triaged (some pre-existing). One judgment: isConsistentSubtype := (coerce …).isSome.
…al; remove toBool hack

coerce: distinguish the boxable dynamic type (Any, as BOTH .TCore "Any" and gradual
.UserDefined) from pure wildcards (Unknown). Concrete .TCore containers (ListAny/
DictStrAny) box/unbox against Any via the realizer (from_ListAny/Any..as_ListAny!/...)
instead of being mis-classified as no-op gradual. Translation: Python float -> TReal
(one domain with the LiteralDecimal literal + from_float:real).

Removed the applyToBool/toBool side-mechanism (a second coercer beside coerce — the
banned two-judgments pattern). Bool-context sites (if/while/assert/assume) route the
condition through the single coerce path (Check.resolveStmtExpr cond TBool) as before.

Unit suite (--solver z3): 160 success / 4 fail / 47 inconclusive / 9 internal / 0 crash.
Above the 159 baseline, with the clean generic-resolver-coerces / caller-realizes design.
…e toBool side-hook

T -> bool truthiness is a real coercion in a gradual language, so it is now a verdict
of the ONE coerce judgment (.truthify source), realized by the ONE realizer
(int_to_bool/str_to_bool/Any_to_bool/...). Removed the applyToBool/toBool side-mechanism;
bool-context sites route through the single coerce path. coerce: when sup=TBool and sub
is concrete, return .truthify sub (this extends the decision: concrete T is now
consistent-subtype of bool, correct for Python boolean context).

Unit suite (--solver z3): 164 success / 4 fail / 47 inconclusive / 5 internal / 0 crash.
Architecture clean: one judgment (isConsistentSubtype := (coerce).isSome), generic
resolver decides, Python realizer maps verdict->runtime call. The 5 internal are all
pre-existing baseline items (field_write, 2 __main__, reassign(ill-typed), type_alias).
Drop the now-unused toBool field/param from TypeLattice, LaurelTranslateOptions, resolve,
and the pipeline call sites — truthiness is a coerce verdict (.truthify) realized by the
one realizer, so the side-channel is gone. No behavior change: 164/4/47/5 unchanged.
Collect module-level 'Name = <type-name>' aliases in translateModule, thread via
TransState.typeAliases into pythonTypeToHighType (now takes an alias map) at every
type-resolution site (proc inputs/outputs/locals/param-copies, module locals). An
annotation referring to an alias resolves to the aliased type instead of a phantom
UserDefined composite. test_type_alias: Internal error -> Analysis success.

Unit suite: 165 success / 4 fail / 47 inconclusive / 4 internal / 0 crash. No regression.
…s prelude stubs

The resolver remaps these builtins to Any_*_to_* names (Resolution.lean builtinContext)
but the prelude never declared 6 of them, so a call like type(e) failed elaboration
(lookupFuncSig miss) instead of producing a sound opaque Any. Declared the 6 missing
stubs in PythonRuntimeLaurelPart (arities per mkBuiltinSig, Any params per convention).

test_invalid_client_type + test_missing_required_param (both use type(e) in an f-string):
Internal error -> Analysis success.

Unit suite: 167 success / 4 fail / 47 inconclusive / 2 internal / 0 crash. The 2 remaining
internal errors are NOT-OUR-BUG ill-formed input: test_field_write (unannotated c = Cell(1))
and test_reassign_different_type (x:int=42; x="hello", mypy-rejected).
Full pipeline writeup (Read -> Resolution -> Translation -> Elaboration -> Laurel
resolve+coerce -> Core), the one-judgment coerce mechanism + caller realizer, files
touched, and honest verification numbers (unit 167/47/4/2; 192/220 match v1 with the
28 differences itemized incl. the 14 precision regressions; benchmark standalone).
@github-actions github-actions Bot added Laurel dependencies Pull requests that update a dependency file labels Jun 25, 2026
… Python-only

The proof-relevant coercion change must not alter the generic Laurel
resolver's behavior for non-Python callers. Three deviations had leaked
into the generic path and broke native diagnostic tests (T22_ArityMismatch,
T23_ArithTyping, T9_IfBranchJoin, ResolutionTypeCheckTests):

1. truthify: `coerce` had a `sup = TBool => some (.truthify sub)` arm that
   made `isConsistentSubtype int bool = true`, breaking native type-checking.
   Removed the `truthify` constructor entirely. Python truthiness needs no
   special verdict: a Python condition is `Any`-typed, so `coerce Any bool =
   project bool`, which the realizer turns into `Any_to_bool`.

2. resolveHighType: the `(expected := …)` kind constraint on `resolveRef`
   had been dropped, suppressing the "resolves to X but expected composite"
   diagnostic. Restored to baseline verbatim.

3. Synth.assign / Check.assign: had been rewritten from baseline CHECK mode
   (`Check.resolveStmtExpr value expectedTy`) to SYNTH mode + a resolver-side
   (T, Error) error-sink expansion. That switched the assignment RHS off the
   check path, changing native diagnostics (whole-expr "cannot apply" instead
   of per-operand "expected/got", wrong spans, swallowed arity errors). The
   expansion was redundant: the elaborator's projProcedureCall already lowers
   an exception-throwing call into a multi-target `(result, maybe_except) :=
   f(args)` BEFORE resolution. Reverted both arms to baseline; Python RHS
   coercion still flows through the one `coerceTo` chokepoint in the
   subsumption fallback of Check.resolveStmtExpr.

Also: import the two new FineGrainLaurel modules from Strata.lean (the FE
package lives outside Strata, so nothing else makes them transitively
imported), and drop an unused `iden` binding.

Verified: brazil-build Strata green end-to-end (all 256 modules imported,
all diagnostic + SMT tests pass). Synth.assign/Check.assign/resolveHighType
are byte-identical to baseline HEAD.
@ssomayyajula
ssomayyajula force-pushed the ssomayyajula/incremental-hybrid-experiment-v4 branch from 7a4d54f to cfb36b6 Compare June 29, 2026 01:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file Laurel

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant