Class AbstractSyntheticGenerator
- Direct Known Subclasses:
CloseGenerator, CompareGenerator, CopyGenerator, DelegationGenerator, DerivedComparisonGenerator, EnumCompareGenerator, EnumConstructorGenerator, EnumEqualsGenerator, EnumFirstLastGenerator, EnumHashCodeGenerator, EnumIncrementDecrementGenerator, EnumIsSetGenerator, EnumIteratorGenerator, EnumJsonGenerator, EnumStringGenerator, EnumStringParamGenerator, EqualsGenerator, FieldSetStatusGenerator, FunctionIsSetGenerator, HashCodeGenerator, InheritedDispatcherGenerator, IsSetGenerator, NotEqualsGenerator, ToJsonGenerator, ToStringGenerator
Provides common patterns and utilities used by all synthetic generators:
- IsSet guard generation for this and parameters
- Unset return block generation
- Field iteration utilities
- Memory management (RETAIN/SCOPE_REGISTER) patterns
All generated IR follows the EK9 tri-state semantics where operations return unset if any operand is unset.
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionprotected static final recordAn entered cycle guard: the variable holding the owner name, needed again by the matching exit.protected static final recordRecord bundling scope context with initialized instruction list. -
Field Summary
FieldsModifier and TypeFieldDescriptionprotected final IRGenerationContextprotected final SynthesisedCallMetaDataTHE shared source of call metadata for every synthesised call site - use this rather than constructing one per generator (fifteen of them had, before this was hoisted), and never hand-write aCallMetaDataDetailstuple. -
Constructor Summary
ConstructorsModifierConstructorDescriptionprotectedAbstractSyntheticGenerator(IRGenerationContext stackContext) Create a new synthetic generator with the given context. -
Method Summary
Modifier and TypeMethodDescriptionprotected booleanaggregateHasOperator(AggregateSymbol aggregateSymbol, String operatorName) Check if the given aggregate has a specific operator defined (in its own scope).protected SyntheticMethodCallBuilderCreate a builder for a constructor call.protected DebugInfocreateDebugInfo(ISymbol symbol) Create debug info from a symbol's source token.protected SynthesisScopecreateSynthesisScope(ISymbol symbol, String scopeLabel) Create an SynthesisScope for the given operator symbol.protected booleanfieldTypeHasRealOperator(ISymbol field, String glyph, boolean binary) Does the field's TYPE have a REALoperatorGlyphof its own (declared or inherited from a real super), as opposed to only the degenerateAnydefault that every type inherits?generateBinaryOperatorGuards(String aggregateTypeName, ISymbol aggregateType, String paramName, String unsetLabel, SynthesisScope ctx) Generate isSet guards for binary operators (this and param).generateBooleanBranch(String booleanVar, String label, boolean branchIfTrue, SynthesisScope ctx) Extract primitive boolean from Boolean object and branch.generateCycleGuardedRegion(String exitMethod, AbstractSyntheticGenerator.CycleGuardEntry entry, List<IRInstr> body, String endLabel, SynthesisScope ctx) THE guarded region of a cycle-guarded operator whose exit returns nothing (?,==,<=>): the body runs as the try of a TRY_CATCH_FINALLY with NO catches whose finally is the guard's exit, so the exit runs on every path - including an exception thrown by a field's hand-written operator, which would otherwise leave this object's entry behind and make a later call read as a cycle (Contract 13).generateCycleGuardEnter(String enterMethod, AggregateSymbol aggregateSymbol, String otherVar, String onBackEdgeLabel, SynthesisScope ctx) Enter the runtime cycle guard for this object (and, for a comparison, the other operand):generateFieldLoad(String targetVar, String objectVar, String fieldName, String ownerTypeName, String fieldTypeName, DebugInfo debugInfo, String scopeId) Generate field load instruction with memory management using explicit LOAD_FIELD.generateFieldLoad(String targetVar, String objectVar, String fieldName, String ownerTypeName, String fieldTypeName, SynthesisScope ctx) Generate field load using SynthesisScope with explicit type information.generateFieldSetStatusCheck(String otherParamName, AggregateSymbol aggregateSymbol, DebugInfo debugInfo, String scopeId, String returnUnsetLabel) Generate field set status comparison check.generateIsSetGuard(String variableName, String typeName, ISymbol type, String unsetLabel, SynthesisScope ctx) Generate isSet guard check for a variable using SynthesisScope.generateIsSetGuard(String variableName, String typeName, ISymbol type, DebugInfo debugInfo, String unsetLabel, String scopeId) Generate isSet guard check for a variable with branch to unset return.protected StringgenerateLabelName(String prefix) Generate a label name with the given prefix.generatePoisonableCycleGuardedRegion(String exitMethod, AbstractSyntheticGenerator.CycleGuardEntry entry, List<IRInstr> body, String unsetLabel, String keepLabel, SynthesisScope ctx) THE guarded region of a representation ($,$$): asgenerateCycleGuardedRegion(String, AbstractSyntheticGenerator.CycleGuardEntry, List, String, SynthesisScope), but the exit in the finally also says whether a cycle was met while the body ran.generateResultReturnBlock(String labelName, String returnVarName, DebugInfo debugInfo, String scopeId) Generate a result return block that returns whatever is in the return variable.generateResultReturnBlock(String labelName, String returnVarName, SynthesisScope ctx) Generate result return block using SynthesisScope.generateStringLiteralLoad(String resultVar, String literalValue, DebugInfo debugInfo, String scopeId) Generate a string literal load with memory management.protected StringGenerate a temporary variable name.generateThisIsSetGuard(String aggregateTypeName, ISymbol aggregateType, String unsetLabel, SynthesisScope ctx) Generate isSet guard for 'this' using SynthesisScope.generateThisIsSetGuard(String aggregateTypeName, ISymbol aggregateType, DebugInfo debugInfo, String unsetLabel, String scopeId) Generate isSet guard check for 'this' with branch to unset return.generateUnsetReturnBlockWithLabel(String labelName, String returnTypeName, String returnVarName, DebugInfo debugInfo, String scopeId) Generate unset return block at a specific label.generateUnsetReturnBlockWithLabel(String labelName, String returnTypeName, String returnVarName, SynthesisScope ctx) Generate unset return block using SynthesisScope.protected ISymbolThe Any type SYMBOL - the root of every EK9 type, a Java interface at the backend.protected StringThe Any type name - seegetAnyType().protected ISymbolThe Bits type SYMBOL - seegetBooleanType().protected StringGet the Bits type name from the EK9 type system.protected ISymbolThe Boolean type SYMBOL - for reading call metadata off it, rather than only naming it.protected StringGet the Boolean type name from the EK9 type system.protected ISymbolThe Integer type SYMBOL - seegetBooleanType().protected StringGet the Integer type name from the EK9 type system.protected ReturnBlockHelperGet the shared ReturnBlockHelper instance.protected ISymbolThe String type SYMBOL - seegetBooleanType().protected StringGet the String type name from the EK9 type system.protected StringgetSuperTypeName(AggregateSymbol aggregateSymbol) Get the fully qualified name of the super aggregate.getSyntheticFields(AggregateSymbol aggregateSymbol) Get all fields from an aggregate that should be included in synthetic operations.protected ISymbolThe same type, as a SYMBOL rather than a name - so a call ON this value can derive its dispatch flag instead of inheriting the builder's shape-only default.protected StringgetTypeName(ISymbol symbol) Get the fully qualified name for a type.protected StringGet the Void type name from the EK9 type system.protected AbstractSyntheticGenerator.ScopeSetupinitializeScope(MethodSymbol operatorSymbol, String scopeLabel, String returnVarName, String returnTypeName) Initialize scope with return variable reference.protected AbstractSyntheticGenerator.ScopeSetupinitializeScopeNoReturn(MethodSymbol operatorSymbol, String scopeLabel) Initialize scope for void-returning methods (no return variable).protected booleanisAnyType(IAggregateSymbol type) Check if the given type is the Any type.protected booleanisTraitType(ISymbol symbol) Check if the given symbol's type is a trait.protected SyntheticMethodCallBuilderCreate a builder for a method call.protected Optional<MethodSymbol> nearestSuperOperator(IAggregateSymbol aggregateSymbol, String operatorName) THE super operator a generated operator calls: the NEAREST declaration above the type, across the whole super chain (InheritedOperators.nearestAbove(IAggregateSymbol, String, ISymbol), neverAny's), when it has a body.protected booleanneedsCycleGuard(AggregateSymbol aggregateSymbol) Does this aggregate's generated operator need the runtime cycle guard? Only a type phase 5 found can reach itself through its properties (PropertyCycles); for every other type the generated IR is unchanged.protected booleanrendersNothing(AggregateSymbol aggregateSymbol, String operatorName) THE decision shared by the REPRESENTATION operators '$' (_string) and '$$' (_json): does an object of this type have NOTHING to render? When it does not, the generated operator is just "return unset".protected StringservingOperatorParameterTypeName(IAggregateSymbol aggregate, String jvmMethodName) The parameter type of the operator that actually servesthis <op> otherwhenotheris this aggregate too - THE descriptor a synthesised delegation (<>to==,<and the rest to<=>) must call.protected StringsuperCallOwner(IAggregateSymbol aggregateSymbol, String operatorName) The fully qualified name of the type that OWNS the nearest super operator - the type a generated super call names (Contract 19).protected booleansuperContributesRepresentation(IAggregateSymbol aggregateSymbol, String operatorName) Does this type's super contribute to its '$' / '$$'? Only when the NEAREST operator above it (nearestSuperOperator(IAggregateSymbol, String)) can render something: never an abstract one (nothing to call), a hand-written one always may (it is honoured, whatever it returns), a GENERATED one only when its owner's chain has fields to render (GeneratedOperatorFields.anyIncludingInherited(IAggregateSymbol)).protected booleansuperHasOperator(AggregateSymbol aggregateSymbol, String operatorName) Check if the nearest super operator of that name (nearestSuperOperator(IAggregateSymbol, String)) exists with a body: an abstract declaration is not one the generated operator can call.
-
Field Details
-
stackContext
-
synthesisedCallMetaData
THE shared source of call metadata for every synthesised call site - use this rather than constructing one per generator (fifteen of them had, before this was hoisted), and never hand-write aCallMetaDataDetailstuple. See its javadoc for the two honest sources.
-
-
Constructor Details
-
AbstractSyntheticGenerator
Create a new synthetic generator with the given context.- Parameters:
stackContext- The IR generation context
-
-
Method Details
-
getReturnBlockHelper
Get the shared ReturnBlockHelper instance.Lazily initializes a single ReturnBlockHelper per generator instance, avoiding the need to create a new instance in each generate() method.
- Returns:
- The shared ReturnBlockHelper instance
-
methodCall
Create a builder for a method call.- Returns:
- A new SyntheticMethodCallBuilder configured for method calls
-
constructorCall
Create a builder for a constructor call.- Returns:
- A new SyntheticMethodCallBuilder configured for constructor calls
-
getTypeName
-
getType
The same type, as a SYMBOL rather than a name - so a call ON this value can derive its dispatch flag instead of inheriting the builder's shape-only default.Pass this wherever
getTypeName(ISymbol)was feeding a.on(receiver, name): the two then answer from one place, and a field whose type is CLOSED (a record, an enum, a non-open class, or a closed built-in like Boolean or JSON) stops rendering its operator calls as dispatchable. -
getBooleanTypeName
Get the Boolean type name from the EK9 type system. -
getBooleanType
The Boolean type SYMBOL - for reading call metadata off it, rather than only naming it. -
getIntegerType
The Integer type SYMBOL - seegetBooleanType(). -
getIntegerTypeName
Get the Integer type name from the EK9 type system. -
getStringTypeName
Get the String type name from the EK9 type system. -
getStringType
The String type SYMBOL - seegetBooleanType(). -
getBitsTypeName
Get the Bits type name from the EK9 type system. -
getBitsType
The Bits type SYMBOL - seegetBooleanType(). -
getAnyType
The Any type SYMBOL - the root of every EK9 type, a Java interface at the backend. Used as the fall-back receiver when a synthesiseddefault operatorruns over a field whose type has no REAL operator of its own (seefieldTypeHasRealOperator(ISymbol, String, boolean)). -
getAnyTypeName
The Any type name - seegetAnyType(). -
fieldTypeHasRealOperator
Does the field's TYPE have a REALoperatorGlyphof its own (declared or inherited from a real super), as opposed to only the degenerateAnydefault that every type inherits?A synthesised
default operator(<=>/==/$/#?) over a field may direct-call the field type's own operator ONLY when this is true. When false - the case for a monomorphised generic field whose T is a non-capable type (e.g.List of Stringhas no real<=>) - the generator must instead callAny's operator, otherwise it emits a call to a symbol that no object defines (a native eager-link failure; the JVM only tolerates it by lazy dispatch onto theAnyinterface default). Uses the sharedHasRealOperatorwalk.- Parameters:
field- the field symbolglyph- the operator glyph ("<=>","==","$","#?")binary- true for a self-typed single-argument operator (<=>,==); false for a no-argument operator ($,#?)
-
servingOperatorParameterTypeName
The parameter type of the operator that actually servesthis <op> otherwhenotheris this aggregate too - THE descriptor a synthesised delegation (<>to==,<and the rest to<=>) must call. Resolved with the sharedOperatorTargetMethodResolver, never assumed to be the aggregate itself: a user'soperator == (arg as Any)servesself == selfas_eq(Any), and a delegation naming_eq(Self)compiled clean and then threw NoSuchMethodError (front-end audit FE74, C1).- Parameters:
aggregate- the aggregate the operator is synthesised onjvmMethodName- the delegate's JVM method name (_eq,_cmp)
-
getVoidTypeName
Get the Void type name from the EK9 type system. -
generateTempName
Generate a temporary variable name. -
generateLabelName
-
createDebugInfo
-
createSynthesisScope
Create an SynthesisScope for the given operator symbol.This bundles debugInfo and scopeId together for cleaner method signatures. Use this at the start of a generate() method:
final var ctx = createSynthesisScope(operatorSymbol, "_eq"); // Use ctx.debugInfo() and ctx.scopeId() or pass ctx to methods
- Parameters:
symbol- The method symbol to get debug info fromscopeLabel- The label for the scope (e.g., "_eq", "_cmp", "_hashcode")- Returns:
- SynthesisScope bundling debugInfo and scopeId
-
initializeScope
protected AbstractSyntheticGenerator.ScopeSetup initializeScope(MethodSymbol operatorSymbol, String scopeLabel, String returnVarName, String returnTypeName) Initialize scope with return variable reference.This consolidates the common boilerplate at the start of every generate() method:
final var ctx = createSynthesisScope(operatorSymbol, "_eq"); final var instructions = new ArrayList<IRInstr>(); instructions.add(ScopeInstr.enter(ctx.scopeId(), ctx.debugInfo())); instructions.add(MemoryInstr.reference(RETURN_VAR, getBooleanTypeName(), ctx.debugInfo()));
Becomes:
final var setup = initializeScope(operatorSymbol, "_eq", RETURN_VAR, getBooleanTypeName()); final var ctx = setup.ctx(); final var instructions = setup.instructions();
- Parameters:
operatorSymbol- The operator method symbol for debug infoscopeLabel- The scope label (e.g., "_eq", "_cmp")returnVarName- The return variable name (typically "rtn")returnTypeName- The return type name- Returns:
- ScopeSetup containing context and initialized instruction list
-
initializeScopeNoReturn
protected AbstractSyntheticGenerator.ScopeSetup initializeScopeNoReturn(MethodSymbol operatorSymbol, String scopeLabel) Initialize scope for void-returning methods (no return variable).Use this for operators like ++ and -- that don't return a value.
- Parameters:
operatorSymbol- The operator method symbol for debug infoscopeLabel- The scope label (e.g., "_inc", "_dec")- Returns:
- ScopeSetup containing context and initialized instruction list
-
generateBinaryOperatorGuards
protected List<IRInstr> generateBinaryOperatorGuards(String aggregateTypeName, ISymbol aggregateType, String paramName, String unsetLabel, SynthesisScope ctx) Generate isSet guards for binary operators (this and param).Most binary operators (==, <>, <=>) need to check that both this and the parameter are set before comparing. This helper consolidates the common two-guard pattern:
instructions.addAll(generateThisIsSetGuard(aggregateTypeName, returnUnsetLabel, ctx)); instructions.addAll(generateIsSetGuard(paramName, aggregateTypeName, returnUnsetLabel, ctx));
- Parameters:
aggregateTypeName- The fully qualified type name of both this and paramparamName- The parameter variable name (typically "param")unsetLabel- Label to branch to if either is unsetctx- Synthesis scope context- Returns:
- List of IR instructions for both guards
-
generateThisIsSetGuard
protected List<IRInstr> generateThisIsSetGuard(String aggregateTypeName, ISymbol aggregateType, DebugInfo debugInfo, String unsetLabel, String scopeId) Generate isSet guard check for 'this' with branch to unset return.Pattern:
_temp = CALL this._isSet() -> Boolean RETAIN _temp SCOPE_REGISTER _temp, scope_id _temp_val = UNBOX _temp -> boolean BRANCH_IF_FALSE _temp_val -> unset_label
- Parameters:
aggregateTypeName- The fully qualified type name of the aggregatedebugInfo- Debug information for the instructionsunsetLabel- Label to branch to if unsetscopeId- Current scope ID for memory management- Returns:
- List of IR instructions for the guard
-
generateIsSetGuard
protected List<IRInstr> generateIsSetGuard(String variableName, String typeName, ISymbol type, DebugInfo debugInfo, String unsetLabel, String scopeId) Generate isSet guard check for a variable with branch to unset return.The check generates:
1. _temp = CALL variable._isSet() -> Boolean 2. RETAIN/SCOPE_REGISTER _temp 3. _tempBool = CALL _temp._true() -> boolean (primitive) 4. RETAIN/SCOPE_REGISTER _tempBool 5. BRANCH_FALSE _tempBool, unsetLabel
- Parameters:
variableName- The variable to checktypeName- The fully qualified type name of the variabletype- That type as a SYMBOL, so the _isSet metadata is read rather than defaulteddebugInfo- Debug information for the instructionsunsetLabel- Label to branch to if unsetscopeId- Current scope ID for memory management- Returns:
- List of IR instructions for the guard
-
rendersNothing
THE decision shared by the REPRESENTATION operators '$' (_string) and '$$' (_json): does an object of this type have NOTHING to render? When it does not, the generated operator is just "return unset".🔑 Decided at COMPILE time, from the TYPE, never at run time (Defect 7, 2026-10-07). The rule was once a runtime test of
this._fieldSetStatus()._empty(), and that call is VIRTUAL: a super's generated$reached throughsuper.$()on a field-less sub-type - or simply INHERITED by one - read the SUB-type's empty status and returned unset, soNamed extends Personwith no properties of its own rendered$as unset and$$as{}. A type knows its own fields; asking the object which runtime class it is gave the wrong type's answer.A type renders nothing when it has no fields of its own (properties, or a dynamic class's captures) AND its super contributes nothing (
superContributesRepresentation(IAggregateSymbol, String)). So a field-less sub-type of a fielded super renders the super's fields, nested (Named(Person(name=Ada))), and a chain with no properties anywhere has nothing to render.🛑 The test is "there are no fields", never "no fields are set". A wholly unset record still renders -
JsonAllFieldsUnsetTestpins{"name":null,...}- and what makes a partial object's '$' unset is copy-left String join inToStringGenerator:String._addis set only when both operands are set. '$$' has no such propagation (the JSON constructor renders an unset field asnull), which is why '$' is UNSET for a partial object while '$$' stays SET. That asymmetry is deliberate.Deliberately does NOT consult '?'. '?' is AND over every field (see
IsSetGenerator), so gating a representation on it would make a partial object render as nothing at all. SeeHashCodeGenerator, which takes the opposite decision for '#?': a hash identifies a VALUE, a representation renders what data is present.- Parameters:
aggregateSymbol- The aggregate whose '$' or '$$' is being generatedoperatorName- "$" or "$$"- Returns:
- true when the generated operator should simply return unset
-
superContributesRepresentation
protected boolean superContributesRepresentation(IAggregateSymbol aggregateSymbol, String operatorName) Does this type's super contribute to its '$' / '$$'? Only when the NEAREST operator above it (nearestSuperOperator(IAggregateSymbol, String)) can render something: never an abstract one (nothing to call), a hand-written one always may (it is honoured, whatever it returns), a GENERATED one only when its owner's chain has fields to render (GeneratedOperatorFields.anyIncludingInherited(IAggregateSymbol)). A generated operator on a field-less chain always returns unset, so folding it in would make every sub-type unset - or, for '$$', an empty object - for no reason; it is skipped instead."Nearest" walks the whole super chain (C2, 2026-10-08): a field-less type between the owner and this one declares nothing and adds no layer -
Leaf(Base(id=1), name=leaf).- Parameters:
aggregateSymbol- The aggregate whose '$' or '$$' is being generatedoperatorName- "$" or "$$"- Returns:
- true when the super's operator is to be called and folded in
-
nearestSuperOperator
protected Optional<MethodSymbol> nearestSuperOperator(IAggregateSymbol aggregateSymbol, String operatorName) THE super operator a generated operator calls: the NEAREST declaration above the type, across the whole super chain (InheritedOperators.nearestAbove(IAggregateSymbol, String, ISymbol), neverAny's), when it has a body. Its OWNER is the type a generatedCALL (Owner)super._x()names (Contract 19) - so callsuperCallOwner(IAggregateSymbol, String)for that name, never the immediate super's.- Parameters:
aggregateSymbol- The aggregate whose operator is being generatedoperatorName- The operator name (e.g., "?", "$", "==")- Returns:
- the operator to call, if there is one with a body
-
superCallOwner
The fully qualified name of the type that OWNS the nearest super operator - the type a generated super call names (Contract 19). Only call this whennearestSuperOperator(IAggregateSymbol, String)found one. -
getSyntheticFields
Get all fields from an aggregate that should be included in synthetic operations.This returns only the direct properties of the aggregate, not inherited ones. Inherited fields are handled by calling super's synthetic operators.
For dynamic classes/functions with captured variables, those captured fields are stored in a separate CaptureScope. This method includes them so that synthetic operators like _isSet and _fieldSetStatus correctly reflect captured field state.
- Parameters:
aggregateSymbol- The aggregate to get fields from- Returns:
- List of field symbols
-
needsCycleGuard
Does this aggregate's generated operator need the runtime cycle guard? Only a type phase 5 found can reach itself through its properties (PropertyCycles); for every other type the generated IR is unchanged. -
generateCycleGuardEnter
protected AbstractSyntheticGenerator.CycleGuardEntry generateCycleGuardEnter(String enterMethod, AggregateSymbol aggregateSymbol, String otherVar, String onBackEdgeLabel, SynthesisScope ctx) Enter the runtime cycle guard for this object (and, for a comparison, the other operand):_owner = LOAD_STATIC_FIELD _cycleGuardOwner, module::Type, org.ek9.lang::String // made once, in _c_init _entered = CALL_STATIC Ek9CycleGuard.enterXxx(this[, other], _owner) -> boolean BRANCH_FALSE _entered -> onBackEdgeLabel // already in progress: a cycle
The back-edge path has NOT entered, so it must not run the exit: the guarded region (generateCycleGuardedRegion(String, AbstractSyntheticGenerator.CycleGuardEntry, List, String, SynthesisScope)) starts AFTER this branch. -
generateCycleGuardedRegion
protected List<IRInstr> generateCycleGuardedRegion(String exitMethod, AbstractSyntheticGenerator.CycleGuardEntry entry, List<IRInstr> body, String endLabel, SynthesisScope ctx) THE guarded region of a cycle-guarded operator whose exit returns nothing (?,==,<=>): the body runs as the try of a TRY_CATCH_FINALLY with NO catches whose finally is the guard's exit, so the exit runs on every path - including an exception thrown by a field's hand-written operator, which would otherwise leave this object's entry behind and make a later call read as a cycle (Contract 13). The body decides the result intortnand must be LEFT BY FALLING THROUGH: it may branch only to labels inside itself, ending atendLabel, which this places last. The caller then goes on to returnrtn.SCOPE_ENTER chain_scope CONTROL_FLOW_CHAIN TRY_CATCH_FINALLY try: SCOPE_ENTER try_scope, body..., LABEL endLabel, SCOPE_EXIT try_scope finally: CALL_STATIC Ek9CycleGuard.exitXxx(this[, other], _owner) SCOPE_EXIT chain_scope -
generatePoisonableCycleGuardedRegion
protected List<IRInstr> generatePoisonableCycleGuardedRegion(String exitMethod, AbstractSyntheticGenerator.CycleGuardEntry entry, List<IRInstr> body, String unsetLabel, String keepLabel, SynthesisScope ctx) THE guarded region of a representation ($,$$): asgenerateCycleGuardedRegion(String, AbstractSyntheticGenerator.CycleGuardEntry, List, String, SynthesisScope), but the exit in the finally also says whether a cycle was met while the body ran. After the region, when it was, what was built is discarded (the return variable released) and the operator goes tounsetLabel; otherwise it goes tokeepLabel:CONTROL_FLOW_CHAIN TRY_CATCH_FINALLY try: body... finally: _poisoned = CALL_STATIC Ek9CycleGuard.exitXxx(this, _owner) -> boolean BRANCH_FALSE _poisoned -> keepLabel RELEASE rtn BRANCH unsetLabelThe poisoned flag is read only on the normal path; an exception leaves the operator through the finally. -
superHasOperator
Check if the nearest super operator of that name (nearestSuperOperator(IAggregateSymbol, String)) exists with a body: an abstract declaration is not one the generated operator can call.- Parameters:
aggregateSymbol- The aggregate whose supers we're checkingoperatorName- The operator name (e.g., "==", "<=>")- Returns:
- true if a super has the operator, false otherwise
-
isAnyType
Check if the given type is the Any type. -
isTraitType
Check if the given symbol's type is a trait.Traits in EK9 compile to Java interfaces, so method calls on trait-typed variables must use invokeinterface instead of invokevirtual.
- Parameters:
symbol- The symbol to check (typically a field or variable)- Returns:
- true if the symbol's type is a trait, false otherwise
-
aggregateHasOperator
Check if the given aggregate has a specific operator defined (in its own scope).- Parameters:
aggregateSymbol- The aggregate to checkoperatorName- The operator name (e.g., "?", "$", "#?")- Returns:
- true if the aggregate has the operator, false otherwise
-
getSuperTypeName
Get the fully qualified name of the super aggregate.- Parameters:
aggregateSymbol- The aggregate to get super from- Returns:
- The super type name, or empty string if no super (other than Any)
-
generateFieldLoad
protected List<IRInstr> generateFieldLoad(String targetVar, String objectVar, String fieldName, String ownerTypeName, String fieldTypeName, DebugInfo debugInfo, String scopeId) Generate field load instruction with memory management using explicit LOAD_FIELD.Pattern:
_temp = LOAD_FIELD objectVar, fieldName, ownerTypeName, fieldTypeName RETAIN _temp SCOPE_REGISTER _temp, scope_id
- Parameters:
targetVar- The variable to store the loaded valueobjectVar- The object to load from (typically "this" or "param")fieldName- The field name to loadownerTypeName- The fully qualified type name of the object that owns the fieldfieldTypeName- The fully qualified type name of the fielddebugInfo- Debug informationscopeId- Current scope ID- Returns:
- List of IR instructions
-
generateStringLiteralLoad
protected List<IRInstr> generateStringLiteralLoad(String resultVar, String literalValue, DebugInfo debugInfo, String scopeId) Generate a string literal load with memory management.Pattern:
_temp = LOAD_LITERAL "value", org.ek9.lang::String RETAIN _temp SCOPE_REGISTER _temp, scope_id
- Parameters:
resultVar- Variable to store the loaded literalliteralValue- The string literal valuedebugInfo- Debug informationscopeId- Current scope ID- Returns:
- List of IR instructions
-
generateFieldSetStatusCheck
protected List<IRInstr> generateFieldSetStatusCheck(String otherParamName, AggregateSymbol aggregateSymbol, DebugInfo debugInfo, String scopeId, String returnUnsetLabel) Generate field set status comparison check.This optimization compares the _fieldSetStatus() bitmasks of both objects. If the bitmasks differ, it means different fields are set/unset between the objects, so the comparison result should be unset (tri-state semantics).
Pattern:
_thisStatus = CALL this._fieldSetStatus() -> Bits _otherStatus = CALL other._fieldSetStatus() -> Bits _statusEq = CALL _thisStatus._eq(_otherStatus) -> Boolean _statusEqSet = CALL _statusEq._isSet() -> Boolean BRANCH_IF_FALSE _statusEqSet -> return_unset // shouldn't happen but safe _statusEqVal = CALL _statusEq._true() -> boolean BRANCH_IF_FALSE _statusEqVal -> return_unset // different field set patterns
- Parameters:
otherParamName- The name of the other parameter to compare with (typically "param")aggregateSymbol- The aggregate as a SYMBOL - supplies both the emitted type name and the dispatch flag, so a closed aggregate's _fieldSetStatus() calls are directdebugInfo- Debug informationscopeId- Current scope IDreturnUnsetLabel- Label to branch to if bitmasks differ- Returns:
- List of IR instructions implementing the check
-
generateUnsetReturnBlockWithLabel
protected List<IRInstr> generateUnsetReturnBlockWithLabel(String labelName, String returnTypeName, String returnVarName, DebugInfo debugInfo, String scopeId) Generate unset return block at a specific label.Unlike the other overload of generateUnsetReturnBlockWithLabel, this version uses the provided label name instead of generating a new one. Use this when you need to branch to the unset return from multiple locations.
- Parameters:
labelName- The label name for this blockreturnTypeName- The type of value to returnreturnVarName- The name of the return variabledebugInfo- Debug informationscopeId- Current scope ID- Returns:
- List of IR instructions for the unset return block
-
generateResultReturnBlock
protected List<IRInstr> generateResultReturnBlock(String labelName, String returnVarName, DebugInfo debugInfo, String scopeId) Generate a result return block that returns whatever is in the return variable.This is a shared return point when the return variable has already been set by the caller. Only performs scope cleanup and return.
Pattern:
label_name: SCOPE_EXIT scope_id RETURN returnVarName
- Parameters:
labelName- The label name for this blockreturnVarName- The return variable name (already set by caller)debugInfo- Debug informationscopeId- Current scope ID- Returns:
- List of IR instructions for the return block
-
generateResultReturnBlock
protected List<IRInstr> generateResultReturnBlock(String labelName, String returnVarName, SynthesisScope ctx) Generate result return block using SynthesisScope.Convenience overload that extracts debugInfo and scopeId from the context.
- Parameters:
labelName- The label for this return blockreturnVarName- The return variable name (already set by caller)ctx- Synthesis scope context- Returns:
- List of IR instructions for the return block
-
generateIsSetGuard
protected List<IRInstr> generateIsSetGuard(String variableName, String typeName, ISymbol type, String unsetLabel, SynthesisScope ctx) Generate isSet guard check for a variable using SynthesisScope.- Parameters:
variableName- The variable to checktypeName- The fully qualified type name of the variabletype- That type as a SYMBOL, so the _isSet metadata is read rather than defaultedunsetLabel- Label to branch to if unsetctx- IR generation context- Returns:
- List of IR instructions for the guard
-
generateThisIsSetGuard
protected List<IRInstr> generateThisIsSetGuard(String aggregateTypeName, ISymbol aggregateType, String unsetLabel, SynthesisScope ctx) Generate isSet guard for 'this' using SynthesisScope.- Parameters:
aggregateTypeName- The fully qualified type name of the aggregateunsetLabel- Label to branch to if unsetctx- IR generation context- Returns:
- List of IR instructions for the guard
-
generateFieldLoad
protected List<IRInstr> generateFieldLoad(String targetVar, String objectVar, String fieldName, String ownerTypeName, String fieldTypeName, SynthesisScope ctx) Generate field load using SynthesisScope with explicit type information.- Parameters:
targetVar- The variable to store the loaded valueobjectVar- The object to load from (typically "this" or "param")fieldName- The field name to loadownerTypeName- The fully qualified type name of the object that owns the fieldfieldTypeName- The fully qualified type name of the fieldctx- IR generation context- Returns:
- List of IR instructions
-
generateUnsetReturnBlockWithLabel
protected List<IRInstr> generateUnsetReturnBlockWithLabel(String labelName, String returnTypeName, String returnVarName, SynthesisScope ctx) Generate unset return block using SynthesisScope.- Parameters:
labelName- The label name for this blockreturnTypeName- The type of value to returnreturnVarName- The name of the return variablectx- IR generation context- Returns:
- List of IR instructions for the unset return block
-
generateBooleanBranch
protected List<IRInstr> generateBooleanBranch(String booleanVar, String label, boolean branchIfTrue, SynthesisScope ctx) Extract primitive boolean from Boolean object and branch.This consolidates the common pattern of calling _true() on a Boolean object to get a primitive boolean, then branching based on the result.
Pattern:
_temp = CALL booleanVar._true() -> boolean BRANCH_IF_TRUE/FALSE _temp, label
- Parameters:
booleanVar- The Boolean variable to extract value fromlabel- The label to branch tobranchIfTrue- If true, branch when Boolean is true; if false, branch when falsectx- Synthesis scope context- Returns:
- List of IR instructions
-