fix: move generics into a lowerer instead of resolver phase#1820
Open
ghaith wants to merge 7 commits into
Open
fix: move generics into a lowerer instead of resolver phase#1820ghaith wants to merge 7 commits into
ghaith wants to merge 7 commits into
Conversation
…tion When lowering a call to an aggregate-returning generic function, the lowerer reset the call operator to the bare generic name and relied on re-annotation to re-resolve it. For a call whose concrete monomorphization did not exist, that re-resolution ran against the injected aggregate return buffer and bound the call to `<fn>__STRING`, reinterpreting the argument's bytes as a string. For values without an early null byte this read out of bounds and aborted at runtime. Reset the operator to the resolved monomorphization name instead. A generic call whose monomorphization is not declared is now an E048 unresolved-reference error at compile time, consistent with how scalar generics already surface a missing implementation. Also add the TO_STRING__<T> / TO_WSTRING__<T> monomorphizations for the scalar, bit-string, real and date/time types that already ship a typed <T>_TO_STRING / <T>_TO_WSTRING conversion, so TO_STRING/TO_WSTRING work for them. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Compare TO_WSTRING(DINT#42) against a WSTRING literal instead of round-tripping it back through TO_STRING to print it. This is clearer, avoids the odd double conversion, and checks the TO_WSTRING__DINT binding directly. Also drop an internal tracker reference from the two conversion lit tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extract get_generic_candidate, get_specific_function_annotation and derive_generic_types from TypeAnnotator into module-level free functions parameterized by the index (and annotations / new_index). The existing methods now delegate, so first-pass behavior is unchanged. This lets a later generic monomorphization lowering phase reuse the exact same resolution logic without a TypeAnnotator. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…core
Add src/lowering/generics.rs with the reusable core for the upcoming generic
monomorphization lowering phase: a recursive type-declaration substitutor
(T -> concrete, recursing through array/pointer/{sized} vararg element types)
and a materializer that builds an {external} monomorph POU + empty-body
implementation from a generic template (aggregate returns preserved for the
aggregate lowerer). Unit-tested in isolation; wired into the pipeline as a
participant in the next step (allow(dead_code) until then).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Resolve non-builtin generic function calls in a new GenericLowerer
lowering phase rather than the type-annotator's first pass. The phase
materializes each monomorphization as an {external} POU in the AST,
rewrites the call operator to the concrete name, then re-indexes and
re-annotates to a fixed point.
Materialized monomorphizations are real AST POUs, so they survive every
re-index and downstream passes (aggregate-return lowering, codegen) only
see concrete calls. A missing monomorphization becomes a link-time
undefined symbol, uniform for scalar and aggregate returns, instead of
the aggregate-return lowerer re-resolving generics and risking a
byte-reinterpreting mis-dispatch.
Builtin generics (MUX/SEL/SHL/...) have no monomorphization and stay
resolved by the annotator. Generic-nature (E062) checks move into the
lowering phase. Sized-variadic arguments with a numeric element type are
coerced to that element type instead of being C-promoted.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Conflict in src/lowering/calls.rs: reconcile master's #1794 restructure of visit_call_statement (deferred_reference for oversized C string returns + the flags tuple) with the rework's removal of generic handling from the aggregate-return lowerer. Kept master's structure and dropped the generic bits (generic_name/is_generic_function, the operator-rewrite block, and collect_generic_nature_violations) — the GenericLowerer phase rewrites generic calls to concrete monomorphs before this lowerer runs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Build Artifacts🐧 Linux
From workflow run 🪟 Windows
From workflow run |
Monomorphizations are no longer required to be declared in ST. The compiler
emits an {external} declaration for any monomorphization that isn't provided,
so a missing implementation fails uniformly at link time (scalar and aggregate
return types alike) rather than aggregate returns erroring at compile time.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Move generic function-call resolution out of the type annotator's first pass into a dedicated
GenericLowererlowering phase that materializes each monomorphization as a real{external}POUin the AST. This makes scalar and aggregate-return generics behave uniformly and turns a
genuinely-missing monomorphization into a clean link-time error instead of a silent,
byte-reinterpreting mis-dispatch (which could segfault for e.g.
TO_STRING/TO_WSTRINGon numericor unsupported types).
Background
Generic monomorphizations used to be resolved during annotation and registered only as synthetic
index entries — never real declarations that survive a re-index. This caused a scalar-vs-aggregate
divergence:
bind e.g.
TO_STRING(aDint)toTO_STRING__STRINGand reinterpret the argument's bytes → garbageor a crash.
The root cause was that the aggregate lowerer was effectively re-doing the resolver's job.
What changed
GenericLowererparticipant runs (post_annotate) before the aggregate-return lowerer.For each non-builtin generic call it:
{external}POU (with concrete-type substitution) when no real POUalready provides it,
(aggregate-return lowering, codegen) only ever see concrete calls; and a missing monomorphization
is a link-time undefined symbol, uniform for scalar and aggregate returns.
MUX/SEL/SHL/MOVE/…) have no monomorphization and are resolved inlineby codegen, so their resolution stays in the annotator.
E062) violations are now reported by the lowering phase.in-lowering nature check are removed).
Behavior changes worth noting
rather than compile-time
E048.qualified_name: "FN__T")instead of the generic plus a
call_nameredirect.{sized} T...) variadic with a numeric element type arenow coerced to that element type instead of being C-promoted (a promoted value would overflow the
narrower array slot).
Testing
exercise the lowering phase (new
test_utilshelpers run it).cargo xtask litgreen; the unsupported-conversion test now expects the link-time undefined symbol.TO_STRING/TO_WSTRING/MAX/ABS/CONCAT/LENresolve and runcorrectly, and that an unsupported type produces a clean
undefined symbolerror.Follow-up (separate PR)
--no-undefinedlinker gate (default-on for shared objects, with an opt-out flag) so a missingmonomorphization fails at build-invocation time rather than only at final link.