visitAssignmentExpression method
Visit a SAssignmentExpression.
Implementation
@override
Object? visitAssignmentExpression(SAssignmentExpression node) {
final lhs = node.leftHandSide;
// Evaluate RHS once, used by multiple branches below
Object? rhsValue = node.rightHandSide!.accept<Object?>(this);
// Handle suspension on the right-hand side
if (rhsValue is AsyncSuspensionRequest) {
Logger.debug(
"[visitAssignmentExpression] RHS suspended. Propagating suspension.",
);
// The state machine (_determineNextNodeAfterAwait) will handle resumption.
// It needs to know this SAssignmentExpression was the context.
return rhsValue;
}
// END NEW
final operatorType = node.operator;
// Case 1: Simple variable assignment (lexical or implicit this)
if (lhs is SSimpleIdentifier) {
final variableName = lhs.name;
Environment? definingEnv = environment.findDefiningEnvironment(
variableName,
);
if (definingEnv != null) {
// Check if the variable is a LateVariable
final variableValue = definingEnv.get(variableName);
if (variableValue is LateVariable) {
if (operatorType == '=') {
// Simple assignment to late variable
variableValue.assign(rhsValue);
return rhsValue;
} else {
// Compound assignment to late variable
final currentValue =
variableValue.value; // May throw if not initialized
Object? newValue = computeCompoundValue(
currentValue,
rhsValue,
operatorType,
);
variableValue.assign(newValue);
return newValue;
}
} else {
// Regular variable handling
if (operatorType == '=') {
final assigned = environment.assign(
variableName,
rhsValue,
); // Use original assign for lexical
_propagateStaticFieldWrite(definingEnv, variableName, rhsValue);
return assigned;
} else {
// Handle compound assignments on lexical variables
final currentValue = environment.get(
variableName,
); // Get from lexical scope
Object? newValue = computeCompoundValue(
currentValue,
rhsValue,
operatorType,
);
final assigned = environment.assign(
variableName,
newValue,
); // Assign back to lexical scope
_propagateStaticFieldWrite(definingEnv, variableName, newValue);
return assigned;
}
}
} else {
try {
final thisInstance = environment.get('this');
if (thisInstance is InterpretedInstance) {
if (operatorType == '=') {
Logger.debug(
"[Assignment - implicit this] Checking for direct setter '$variableName' on ${thisInstance.runtimeType}",
);
final setter = thisInstance.klass.findInstanceSetter(
variableName,
);
if (setter != null) {
Logger.debug(
"[Assignment - implicit this] Found direct setter. Calling...",
);
// Call setter on 'this'
setter.bind(thisInstance).call(this, [rhsValue], {});
return rhsValue; // Assignment expression returns RHS value
} else {
Logger.debug(
"[Assignment - implicit this] No direct setter found. Trying extension setter for '$variableName' on ${thisInstance.runtimeType}",
);
final extensionSetter = environment.findExtensionMember(
thisInstance,
variableName,
);
if (extensionSetter is ExtensionMemberCallable &&
extensionSetter.isSetter) {
Logger.debug(
"[Assignment - implicit this] Found extension setter '$variableName'. Calling...",
);
final extensionPositionalArgs = [
thisInstance, // Target is 'this'
rhsValue, // Value to assign
];
try {
extensionSetter.call(this, extensionPositionalArgs, {});
Logger.debug(
"[Assignment - implicit this] Extension setter call finished.",
);
return rhsValue; // Simple assignment returns RHS
} catch (e) {
throw RuntimeD4rtException(
"Error executing extension setter '$variableName' via implicit 'this': $e",
);
}
} else {
Logger.debug(
"[Assignment - implicit this] No extension setter found for '$variableName'. Falling back to direct field set.",
);
// Assign directly to field on 'this' (original fallback)
// WARNING: This might be incorrect if the intent was purely extension based.
// Dart would typically throw if no setter (direct or extension) exists and no field exists.
// Consider throwing here if direct field assignment isn't desired.
try {
thisInstance.set(variableName, rhsValue, this);
Logger.debug(
"[Assignment - implicit this] Direct field set successful (?).",
);
return rhsValue; // Assignment expression returns RHS value
} on RuntimeD4rtException catch (fieldSetError) {
Logger.debug(
"[Assignment - implicit this] Direct field set failed: ${fieldSetError.message}",
);
// If both direct setter, extension setter, and direct field set fail, THEN throw.
throw RuntimeD4rtException(
"Cannot assign to '$variableName' on implicit 'this': No setter (direct or extension) or assignable field found.",
);
}
}
}
} else {
// 1. Get current value from 'this' (field or getter)
final currentValue = thisInstance.get(
variableName,
); // May throw if undefined on instance
// 2. Calculate new value
Object? newValue = computeCompoundValue(
currentValue,
rhsValue,
operatorType,
);
// 3. Set new value on 'this' (field or setter)
final setter = thisInstance.klass.findInstanceSetter(
variableName,
);
if (setter != null) {
setter.bind(thisInstance).call(this, [newValue], {});
} else {
thisInstance.set(variableName, newValue, this);
}
// Compound assignment returns the NEW value
return newValue;
}
} else if (toBridgedInstance(thisInstance).$2) {
if (rhsValue is BridgedEnumValue) {
rhsValue = rhsValue.nativeValue;
} else if (rhsValue is BridgedInstance) {
// GEN-079: Unwrap BridgedInstance for native setter calls
rhsValue = rhsValue.nativeObject;
}
final bridgedInstance = toBridgedInstance(thisInstance).$1!;
final bridgedClass = bridgedInstance.bridgedClass;
final setterAdapter = bridgedClass.findInstanceSetterAdapter(
variableName,
);
if (setterAdapter != null) {
if (operatorType == '=') {
// Simple assignment: this.bridgedProp = value
Logger.debug(
"[Assignment] Assigning to bridged 'this'.$variableName via setter adapter.",
);
D4.withActiveVisitor<void>(
this,
() => setterAdapter(
this,
thisInstance.nativeObject,
rhsValue,
),
);
return rhsValue; // Simple assignment returns RHS value
} else {
// Compound assignment: this.bridgedProp op= value
// 1. Get current value (requires a getter adapter)
final getterAdapter = bridgedClass.findInstanceGetterAdapter(
variableName,
);
if (getterAdapter == null) {
throw RuntimeD4rtException(
"Cannot perform compound assignment on '${bridgedClass.name}.$variableName' via implicit 'this': No getter found.",
);
}
final currentValue = getterAdapter(
this,
thisInstance.nativeObject,
);
// 2. Calculate new value
Object? newValue = computeCompoundValue(
currentValue,
rhsValue,
operatorType,
);
// 3. Set new value via setter adapter
Logger.debug(
"[Assignment] Compound assigning to bridged 'this'.$variableName via setter adapter.",
);
D4.withActiveVisitor<void>(
this,
() => setterAdapter(
this,
thisInstance.nativeObject,
newValue,
),
);
return newValue; // Compound assignment returns new value
}
} else {
// No setter adapter found
throw RuntimeD4rtException(
"Cannot assign to property '$variableName' on bridged instance of '${bridgedClass.name}' accessed via implicit 'this': No setter found.",
);
}
} else {
// 'this' exists but is not an InterpretedInstance or BridgedInstance
throw RuntimeD4rtException(
"Assigning to undefined variable '$variableName'.",
);
}
} on RuntimeD4rtException catch (e) {
// If 'this' doesn't exist or getting/setting on 'this' failed
// Use the original error if it came from get/set, otherwise standard undefined.
if (e.message.contains("Undefined property '$variableName'") ||
e.message.contains("Undefined static member")) {
rethrow; // Propagate specific error from get/set
}
throw RuntimeD4rtException(
"Assigning to undefined variable '$variableName'.",
);
}
}
}
// Case 2: SPropertyAccess assignment (target.property op= value)
else if (lhs is SPropertyAccess) {
final targetExpression = lhs.target; // Keep expression for check below
final targetValue = targetExpression?.accept<Object?>(this);
final propertyName = lhs.propertyName!.name;
// rhsValue and operatorType already available from the top
if (targetValue is BoundSuper) {
// This handles cases like: super.value = expression; or super.value += expression;
final instance = targetValue.instance;
final startClass = targetValue.startLookupClass;
InterpretedClass? currentClass = startClass;
InterpretedFunction? superSetter;
InterpretedFunction? superGetter;
// Look for the setter in the superclass hierarchy starting from startClass
BridgedClass? bridgedSetter;
while (currentClass != null) {
final setter = currentClass.findInstanceSetter(propertyName);
if (setter != null) {
superSetter = setter;
break;
}
// Check bridged superclass
if (currentClass.bridgedSuperclass != null) {
final bridged = currentClass.bridgedSuperclass!;
if (bridged.setters.containsKey(propertyName)) {
bridgedSetter = bridged;
break;
}
}
currentClass = currentClass.superclass;
}
// For compound operators, we also need to get the current value
Object? currentValue;
BridgedClass? bridgedGetter;
if (operatorType != '=') {
// First try to find a getter
currentClass = startClass;
while (currentClass != null) {
final getter = currentClass.findInstanceGetter(propertyName);
if (getter != null) {
superGetter = getter;
break;
}
// Check bridged superclass
if (currentClass.bridgedSuperclass != null) {
final bridged = currentClass.bridgedSuperclass!;
if (bridged.getters.containsKey(propertyName)) {
bridgedGetter = bridged;
break;
}
}
currentClass = currentClass.superclass;
}
// Get the current value using getter or bridged getter
if (superGetter != null) {
currentValue = superGetter.bind(instance).call(this, [], {});
} else if (bridgedGetter != null) {
// RC-6: Use nativeProxy as fallback for abstract class adapters
final bridgedTarget = instance.bridgedSuperObject ?? instance.nativeProxy;
if (bridgedTarget == null) {
throw RuntimeD4rtException(
"Cannot access bridged property '$propertyName': bridgedSuperObject is null",
);
}
currentValue = bridgedGetter.getters[propertyName]!(
this,
bridgedTarget,
);
} else {
// Try to get field value directly
try {
currentValue = instance.get(propertyName);
} catch (e) {
throw RuntimeD4rtException(
"Cannot read '$propertyName' from superclass chain of '${instance.klass.name}' for compound 'super' assignment: $e",
);
}
}
}
if (operatorType == '=') {
// Simple assignment: super.value = rhsValue
if (superSetter != null) {
superSetter.bind(instance).call(this, [rhsValue], {});
return rhsValue;
} else if (bridgedSetter != null) {
// RC-6: Use nativeProxy as fallback for abstract class adapters
final bridgedTarget = instance.bridgedSuperObject ?? instance.nativeProxy;
if (bridgedTarget == null) {
throw RuntimeD4rtException(
"Cannot set bridged property '$propertyName': bridgedSuperObject is null",
);
}
bridgedSetter.setters[propertyName]!(this, bridgedTarget, rhsValue);
return rhsValue;
} else {
// Try direct field assignment
try {
instance.set(propertyName, rhsValue);
return rhsValue;
} catch (e) {
throw RuntimeD4rtException(
"Setter for '$propertyName' not found in superclass chain of '${instance.klass.name}' for 'super' assignment: $e",
);
}
}
} else {
// Compound assignment: super.value += rhsValue, etc.
// Compute new value
final newValue = computeCompoundValue(
currentValue,
rhsValue,
operatorType,
);
// Set new value
if (superSetter != null) {
superSetter.bind(instance).call(this, [newValue], {});
} else if (bridgedSetter != null) {
// RC-6: Use nativeProxy as fallback for abstract class adapters
final bridgedTarget = instance.bridgedSuperObject ?? instance.nativeProxy;
if (bridgedTarget == null) {
throw RuntimeD4rtException(
"Cannot set bridged property '$propertyName': bridgedSuperObject is null",
);
}
bridgedSetter.setters[propertyName]!(this, bridgedTarget, newValue);
} else {
// Try direct field assignment
try {
instance.set(propertyName, newValue);
} catch (e) {
throw RuntimeD4rtException(
"Cannot set '$propertyName' in superclass chain of '${instance.klass.name}' for compound 'super' assignment: $e",
);
}
}
return newValue;
}
} else if (targetValue is InterpretedInstance) {
// This code block was accidentally removed or modified, restore it.
if (operatorType == '=') {
// Simple assignment: target.property = rhsValue
final setter = targetValue.klass.findInstanceSetter(propertyName);
if (setter != null) {
setter.bind(targetValue).call(this, [rhsValue], {});
Logger.debug(
"[Assignment] Assigned via direct setter for '$propertyName'",
);
return rhsValue;
}
// No direct setter, try extension setter
final extensionSetter = environment.findExtensionMember(
targetValue,
propertyName,
);
if (extensionSetter is ExtensionMemberCallable &&
extensionSetter.isSetter) {
Logger.debug(
"[Assignment] Assigning via extension setter for '$propertyName'",
);
final extensionPositionalArgs = [
targetValue,
rhsValue,
]; // Target + value
try {
extensionSetter.call(this, extensionPositionalArgs, {});
return rhsValue;
} catch (e) {
throw RuntimeD4rtException(
"Error executing extension setter '$propertyName': $e",
);
}
}
// No direct or extension setter, assign to field
Logger.debug(
"[Assignment] No direct or extension setter found for '$propertyName', assigning to field.",
);
targetValue.set(propertyName, rhsValue, this);
return rhsValue; // Simple Assignment returns RHS value
} else {
// Compound assignment: target.property op= rhsValue
// 1. Get current value
final currentValue = targetValue.get(
propertyName,
); // Use instance.get (handles field/getter)
// 2. Calculate new value
Object? newValue = computeCompoundValue(
currentValue,
rhsValue,
operatorType,
);
// 3. Set new value (via setter or direct field access)
final setter = targetValue.klass.findInstanceSetter(propertyName);
if (setter != null) {
setter.bind(targetValue).call(this, [newValue], {});
} else {
// Assign directly to field if no setter (using the instance's set method)
targetValue.set(propertyName, newValue, this);
}
return newValue; // Compound returns new value
}
}
// Handle assignment to static property (targetValue is InterpretedClass)
else if (targetValue is InterpretedClass) {
if (operatorType == '=') {
// Simple assignment: Class.property = rhsValue
final staticSetter = targetValue.findStaticSetter(propertyName);
if (staticSetter != null) {
staticSetter.call(this, [rhsValue], {});
} else {
// Assign directly to static field if no setter
targetValue.setStaticField(propertyName, rhsValue);
}
return rhsValue; // Simple Assignment returns RHS value
} else {
// Compound assignment: Class.property op= rhsValue
// 1. Get current value (static field or getter)
Object? currentValue;
final staticGetter = targetValue.findStaticGetter(propertyName);
if (staticGetter != null) {
currentValue = staticGetter.call(this, [], {});
} else {
// If no getter, try getting the field directly
try {
currentValue = targetValue.getStaticField(propertyName);
} catch (_) {
throw RuntimeD4rtException(
"Cannot get value for compound assignment on static member '$propertyName'. No getter or field found.",
);
}
}
// 2. Calculate new value
Object? newValue = computeCompoundValue(
currentValue,
rhsValue,
operatorType,
);
// 3. Set new value (static setter or direct field access)
final staticSetter = targetValue.findStaticSetter(propertyName);
if (staticSetter != null) {
staticSetter.call(this, [newValue], {});
} else {
targetValue.setStaticField(propertyName, newValue);
}
return newValue; // Compound returns new value
}
} else if (toBridgedInstance(targetValue).$2) {
if (rhsValue is BridgedEnumValue) {
rhsValue = rhsValue.nativeValue;
} else if (rhsValue is BridgedInstance) {
// GEN-079: Unwrap BridgedInstance for native setter calls
rhsValue = rhsValue.nativeObject;
}
final bridgedInstance = toBridgedInstance(targetValue).$1!;
final setterAdapter = bridgedInstance.bridgedClass
.findInstanceSetterAdapter(propertyName);
if (setterAdapter != null) {
if (operatorType == '=') {
// Simple assignment: bridgedInstance.property = value
Logger.debug(
"[Assignment] Assigning to bridged instance property '${bridgedInstance.bridgedClass.name}.$propertyName' via setter adapter.",
);
// Wrap with withActiveVisitor so that D4 helper methods (e.g.
// extractBridgedArg) can access the visitor for interface proxy
// creation when the setter coerces InterpretedInstance arguments.
D4.withActiveVisitor<void>(
this,
() =>
setterAdapter(this, bridgedInstance.nativeObject, rhsValue),
);
return rhsValue; // Simple assignment returns RHS value
} else {
// Compound assignment: bridgedInstance.property op= value
// 1. Get current value (requires a getter adapter)
final getterAdapter = bridgedInstance.bridgedClass
.findInstanceGetterAdapter(propertyName);
if (getterAdapter == null) {
throw RuntimeD4rtException(
"Cannot perform compound assignment on '${bridgedInstance.bridgedClass.name}.$propertyName': No getter adapter found.",
);
}
final currentValue = getterAdapter(
this,
bridgedInstance.nativeObject,
);
// 2. Calculate new value
Object? newValue = computeCompoundValue(
currentValue,
rhsValue,
operatorType,
);
// 3. Set new value via setter adapter
Logger.debug(
"[Assignment] Compound assigning to bridged instance property '${bridgedInstance.bridgedClass.name}.$propertyName' via setter adapter.",
);
// Wrap with withActiveVisitor so that D4 helper methods (e.g.
// extractBridgedArg) can access the visitor for interface proxy
// creation when the setter coerces InterpretedInstance arguments.
D4.withActiveVisitor<void>(
this,
() =>
setterAdapter(this, bridgedInstance.nativeObject, newValue),
);
return newValue; // Compound assignment returns new value
}
} else {
// 1401-TODO #7 (F9): no setter adapter on the bridge. Before
// throwing, check the native↔interpreted reverse map: if this
// native object is the bridged super of an InterpretedInstance
// that declares the property (script-defined field/setter),
// route the assignment to the InterpretedInstance side.
//
// Triggers for scripts that subclass a concrete bridged class
// (e.g. `_RenderMeasureBox extends RenderProxyBox`) and add
// their own fields/setters. The framework hands back the
// native bridged super to script callbacks (e.g.
// `updateRenderObject`), and the regular assignment path
// couldn't reach the script-side onLayout setter without this
// fallback. The native↔interpreted map is populated by
// [D4.extractBridgedArg] when it returns
// `arg.bridgedSuperObject`.
final interpretedObj =
D4.interpretedForNative(bridgedInstance.nativeObject);
if (interpretedObj is InterpretedInstance) {
final scriptSetter =
interpretedObj.klass.findInstanceSetter(propertyName);
final hasField = interpretedObj.klass
.getInstanceFieldNames()
.contains(propertyName);
if (scriptSetter != null || hasField) {
if (operatorType == '=') {
if (scriptSetter != null) {
scriptSetter
.bind(interpretedObj)
.call(this, [rhsValue], {});
} else {
interpretedObj.set(propertyName, rhsValue, this);
}
return rhsValue;
} else {
final getter = interpretedObj.klass
.findInstanceGetter(propertyName);
final Object? currentValue = getter != null
? getter.bind(interpretedObj).call(this, [], {})
: interpretedObj.get(propertyName);
final Object? newValue = computeCompoundValue(
currentValue,
rhsValue,
operatorType,
);
if (scriptSetter != null) {
scriptSetter
.bind(interpretedObj)
.call(this, [newValue], {});
} else {
interpretedObj.set(propertyName, newValue, this);
}
return newValue;
}
}
}
// No setter adapter found and no script-defined fallback.
throw RuntimeD4rtException(
"Cannot assign to property '$propertyName' on bridged instance of '${bridgedInstance.bridgedClass.name}': No setter adapter found.",
);
}
} else if (targetValue is BoundBridgedSuper) {
if (rhsValue is BridgedEnumValue) {
rhsValue = rhsValue.nativeValue;
} else if (rhsValue is BridgedInstance) {
// GEN-079: Unwrap BridgedInstance for native setter calls
rhsValue = rhsValue.nativeObject;
}
// This handles: super.property = rhsValue; or super.property += rhsValue;
final instance = targetValue.instance; // Instance 'this'
final bridgedSuper = targetValue.startLookupClass;
// RC-6: Use nativeProxy as fallback for abstract class adapters
final nativeSuperObject = instance.bridgedSuperObject ?? instance.nativeProxy;
if (nativeSuperObject == null) {
throw RuntimeD4rtException(
"Internal error: Cannot assign to super property '$propertyName' on bridged superclass '${bridgedSuper.name}' because the native super object is missing.",
);
}
// Find the bridged setter adapter
final setterAdapter = bridgedSuper.findInstanceSetterAdapter(
propertyName,
);
if (operatorType == '=') {
// Simple assignment
if (setterAdapter != null) {
try {
// Call the setter adapter with the native object and the new value
D4.withActiveVisitor<void>(
this,
() => setterAdapter(this, nativeSuperObject, rhsValue),
);
return rhsValue; // Assignment returns the right value
} catch (e, s) {
Logger.error(
"Native exception during super assignment to bridged setter '${bridgedSuper.name}.$propertyName': $e\\n$s",
);
throw RuntimeD4rtException(
"Native error during super assignment to bridged setter '$propertyName': $e",
originalException: e,
);
}
} else {
// No setter found
throw RuntimeD4rtException(
"Setter for '$propertyName' not found in bridged superclass '${bridgedSuper.name}' for 'super' assignment.",
);
}
} else {
// Compound assignment: super.property += rhsValue, etc.
// Need both getter and setter
final getterAdapter = bridgedSuper.findInstanceGetterAdapter(
propertyName,
);
if (getterAdapter == null) {
throw RuntimeD4rtException(
"Cannot perform compound assignment on bridged super property '${bridgedSuper.name}.$propertyName': No getter adapter found.",
);
}
if (setterAdapter == null) {
throw RuntimeD4rtException(
"Cannot perform compound assignment on bridged super property '${bridgedSuper.name}.$propertyName': No setter adapter found.",
);
}
try {
// Get current value
final currentValue = getterAdapter(this, nativeSuperObject);
// Compute new value
final newValue = computeCompoundValue(
currentValue,
rhsValue,
operatorType,
);
// Set new value
D4.withActiveVisitor<void>(
this,
() => setterAdapter(this, nativeSuperObject, newValue),
);
return newValue;
} catch (e, s) {
Logger.error(
"Native exception during compound super assignment to bridged property '${bridgedSuper.name}.$propertyName': $e\\n$s",
);
throw RuntimeD4rtException(
"Native error during compound super assignment to bridged property '$propertyName': $e",
originalException: e,
);
}
}
} else {
throw RuntimeD4rtException(
"Assignment target must be an instance, class, or super property, got ${targetValue?.runtimeType}.",
);
}
}
// Case 3: SPrefixedIdentifier assignment (prefix.identifier op= value)
else if (lhs is SPrefixedIdentifier) {
final target = lhs.prefix!.accept<Object?>(this);
final propertyName = lhs.identifier!.name;
// rhsValue and operatorType already available from the top
if (target is InterpretedInstance) {
if (operatorType == '=') {
// Simple assignment: target.property = rhsValue
final setter = target.klass.findInstanceSetter(propertyName);
if (setter != null) {
setter.bind(target).call(this, [rhsValue], {});
Logger.debug(
"[Assignment] Assigned via direct setter for SPrefixedIdentifier '$propertyName'",
);
return rhsValue;
}
final extensionSetter = environment.findExtensionMember(
target,
propertyName,
);
if (extensionSetter is ExtensionMemberCallable &&
extensionSetter.isSetter) {
Logger.debug(
"[Assignment] Assigning via extension setter for SPrefixedIdentifier '$propertyName'",
);
final extensionPositionalArgs = [
target,
rhsValue,
]; // Target + value
try {
extensionSetter.call(this, extensionPositionalArgs, {});
return rhsValue;
} catch (e) {
throw RuntimeD4rtException(
"Error executing extension setter '$propertyName': $e",
);
}
}
Logger.debug(
"[Assignment] No direct or extension setter found for SPrefixedIdentifier '$propertyName', assigning to field.",
);
target.set(propertyName, rhsValue, this);
return rhsValue; // Simple Assignment returns RHS value
} else {
// Compound assignment: target.property op= rhsValue
final currentValue = target.get(propertyName);
Object? newValue = computeCompoundValue(
currentValue,
rhsValue,
operatorType,
);
final setter = target.klass.findInstanceSetter(propertyName);
if (setter != null) {
setter.bind(target).call(this, [newValue], {});
} else {
target.set(propertyName, newValue, this);
}
return newValue; // Compound returns new value
}
} else if (target is InterpretedClass) {
if (operatorType == '=') {
// Simple assignment: Class.property = rhsValue
final staticSetter = target.findStaticSetter(propertyName);
if (staticSetter != null) {
staticSetter.call(this, [rhsValue], {});
} else {
// Assign directly to static field if no setter
target.setStaticField(propertyName, rhsValue);
}
return rhsValue; // Simple Assignment returns RHS value
} else {
// Compound assignment: Class.property op= rhsValue
Object? currentValue;
final staticGetter = target.findStaticGetter(propertyName);
if (staticGetter != null) {
currentValue = staticGetter.call(this, [], {});
} else {
try {
currentValue = target.getStaticField(propertyName);
} catch (_) {
throw RuntimeD4rtException(
"Cannot get value for compound assignment on static member '$propertyName'. No getter or field found.",
);
}
}
Object? newValue = computeCompoundValue(
currentValue,
rhsValue,
operatorType,
);
final staticSetter = target.findStaticSetter(propertyName);
if (staticSetter != null) {
staticSetter.call(this, [newValue], {});
} else {
target.setStaticField(propertyName, newValue);
}
return newValue; // Compound returns new value
}
} else if (target is BridgedClass) {
final bridgedClass = target;
if (operatorType == '=') {
// Simple assignment: BridgedClass.property = rhsValue
final staticSetter = bridgedClass.findStaticSetterAdapter(
propertyName,
);
if (staticSetter == null) {
throw RuntimeD4rtException(
"Bridged class '${bridgedClass.name}' has no static setter named '$propertyName'.",
);
}
Logger.debug(
"[Assignment] Assigning to static bridged property '${bridgedClass.name}.$propertyName' via setter adapter.",
);
staticSetter(this, rhsValue);
return rhsValue; // Simple Assignment returns RHS value
} else {
// Compound assignment: BridgedClass.property op= rhsValue
// 1. Get current static value
final staticGetter = bridgedClass.findStaticGetterAdapter(
propertyName,
);
if (staticGetter == null) {
throw RuntimeD4rtException(
"Cannot perform compound assignment on static '${bridgedClass.name}.$propertyName': No static getter found.",
);
}
final currentValue = staticGetter(this);
// 2. Calculate new value
Object? newValue = computeCompoundValue(
currentValue,
rhsValue,
operatorType,
);
// 3. Set new static value
final staticSetter = bridgedClass.findStaticSetterAdapter(
propertyName,
);
if (staticSetter == null) {
// Should have been caught by getter check, but defensive programming
throw RuntimeD4rtException(
"Cannot perform compound assignment on static '${bridgedClass.name}.$propertyName': No static setter found after getter.",
);
}
Logger.debug(
"[Assignment] Compound assigning to static bridged property '${bridgedClass.name}.$propertyName' via setter adapter.",
);
staticSetter(this, newValue);
return newValue; // Compound returns new value
}
} else if (target is InterpretedExtension) {
final extension = target;
if (operatorType == '=') {
// Simple assignment: Extension.staticField = rhsValue
final staticSetter = extension.findStaticSetter(propertyName);
if (staticSetter != null) {
staticSetter.call(this, [rhsValue], {});
Logger.debug(
"[Assignment] Assigned to static extension property '${extension.name ?? '<unnamed>'}.$propertyName' via setter.",
);
} else if (extension.staticFields.containsKey(propertyName)) {
extension.setStaticField(propertyName, rhsValue);
Logger.debug(
"[Assignment] Assigned to static extension field '${extension.name ?? '<unnamed>'}.$propertyName'.",
);
} else {
throw RuntimeD4rtException(
"Extension '${extension.name ?? '<unnamed>'}' has no static setter or field named '$propertyName'.",
);
}
return rhsValue;
} else {
// Compound assignment: Extension.property op= rhsValue
// 1. Get current value
Object? currentValue;
final staticGetter = extension.findStaticGetter(propertyName);
if (staticGetter != null) {
currentValue = staticGetter.call(this, [], {});
} else if (extension.staticFields.containsKey(propertyName)) {
currentValue = extension.getStaticField(propertyName);
} else {
throw RuntimeD4rtException(
"Cannot get value for compound assignment on static extension member '$propertyName'. No getter or field found.",
);
}
// 2. Calculate new value
Object? newValue = computeCompoundValue(
currentValue,
rhsValue,
operatorType,
);
// 3. Set new value
final staticSetter = extension.findStaticSetter(propertyName);
if (staticSetter != null) {
staticSetter.call(this, [newValue], {});
} else if (extension.staticFields.containsKey(propertyName)) {
extension.setStaticField(propertyName, newValue);
} else {
throw RuntimeD4rtException(
"Cannot set value for compound assignment on static extension member '$propertyName'. No setter or field found.",
);
}
return newValue;
}
} else if (toBridgedInstance(target).$2) {
if (rhsValue is BridgedEnumValue) {
rhsValue = rhsValue.nativeValue;
} else if (rhsValue is BridgedInstance) {
// GEN-079: Unwrap BridgedInstance for native setter calls
rhsValue = rhsValue.nativeObject;
}
final bridgedInstance = toBridgedInstance(target).$1!;
final setterAdapter = bridgedInstance.bridgedClass
.findInstanceSetterAdapter(propertyName);
if (setterAdapter != null) {
if (operatorType == '=') {
// Simple assignment: bridgedInstance.property = value
Logger.debug(
"[Assignment - SPropertyAccess] Assigning to bridged instance property '${bridgedInstance.bridgedClass.name}.$propertyName' via setter adapter.",
);
// Wrap with withActiveVisitor so D4 helpers can resolve the
// visitor for interface proxy creation when coercing args.
D4.withActiveVisitor<void>(
this,
() =>
setterAdapter(this, bridgedInstance.nativeObject, rhsValue),
);
return rhsValue; // Simple assignment returns RHS value
} else {
// Compound assignment: bridgedInstance.property op= value
// 1. Get current value (requires a getter adapter)
final getterAdapter = bridgedInstance.bridgedClass
.findInstanceGetterAdapter(propertyName);
if (getterAdapter == null) {
throw RuntimeD4rtException(
"Cannot perform compound assignment on '${bridgedInstance.bridgedClass.name}.$propertyName': No getter adapter found.",
);
}
final currentValue = getterAdapter(
this,
bridgedInstance.nativeObject,
);
// 2. Calculate new value
Object? newValue = computeCompoundValue(
currentValue,
rhsValue,
operatorType,
);
// 3. Set new value via setter adapter
Logger.debug(
"[Assignment - SPropertyAccess] Compound assigning to bridged instance property '${bridgedInstance.bridgedClass.name}.$propertyName' via setter adapter.",
);
// Wrap with withActiveVisitor so D4 helpers can resolve the
// visitor for interface proxy creation when coercing args.
D4.withActiveVisitor<void>(
this,
() =>
setterAdapter(this, bridgedInstance.nativeObject, newValue),
);
return newValue; // Compound assignment returns new value
}
} else {
// 1401-TODO #7 (F9): SPrefixedIdentifier variant — same shape
// as the SPropertyAccess branch above. The script's
// `renderObject.onLayout = …` may be parsed as a prefixed
// identifier; if the bridge has no `onLayout` setter, check
// the native↔interpreted reverse map for a script-defined
// setter/field on the wrapping InterpretedInstance.
final interpretedObj =
D4.interpretedForNative(bridgedInstance.nativeObject);
if (interpretedObj is InterpretedInstance) {
final scriptSetter =
interpretedObj.klass.findInstanceSetter(propertyName);
final hasField = interpretedObj.klass
.getInstanceFieldNames()
.contains(propertyName);
if (scriptSetter != null || hasField) {
if (operatorType == '=') {
if (scriptSetter != null) {
scriptSetter
.bind(interpretedObj)
.call(this, [rhsValue], {});
} else {
interpretedObj.set(propertyName, rhsValue, this);
}
return rhsValue;
} else {
final getter = interpretedObj.klass
.findInstanceGetter(propertyName);
final Object? currentValue = getter != null
? getter.bind(interpretedObj).call(this, [], {})
: interpretedObj.get(propertyName);
final Object? newValue = computeCompoundValue(
currentValue,
rhsValue,
operatorType,
);
if (scriptSetter != null) {
scriptSetter
.bind(interpretedObj)
.call(this, [newValue], {});
} else {
interpretedObj.set(propertyName, newValue, this);
}
return newValue;
}
}
}
// No setter adapter found and no script-defined fallback.
throw RuntimeD4rtException(
"Cannot assign to property '$propertyName' on bridged instance of '${bridgedInstance.bridgedClass.name}': No setter adapter found.",
);
}
} else {
throw RuntimeD4rtException(
"Assignment target must be an instance or class for SPrefixedIdentifier, got ${target?.runtimeType}.",
);
}
} else {
if (lhs is SIndexExpression) {
final targetValue = lhs.target?.accept<Object?>(this);
final rawIndexValue = lhs.index!.accept<Object?>(this);
// Unwrap BridgedEnumValue for Map key operations
final indexValue = rawIndexValue is BridgedEnumValue
? rawIndexValue.nativeValue
: rawIndexValue;
// Determine the value to actually assign
Object? finalValueToAssign;
if (operatorType == '=') {
finalValueToAssign = rhsValue; // Simple assignment
} else {
// Compound assignment (e.g., list[i] += 10)
// 1. Get current value using index operator []
Object? currentValue;
if (targetValue is Map) {
currentValue = targetValue[indexValue];
} else if (targetValue is List && indexValue is int) {
if (indexValue < 0 || indexValue >= targetValue.length) {
throw RuntimeD4rtException(
'Index out of range for compound assignment read: $indexValue',
);
}
currentValue = targetValue[indexValue];
} else if (targetValue is InterpretedInstance) {
// Check for class operator [] method for reading current value
final operatorMethod = targetValue.findOperator('[]');
if (operatorMethod != null) {
try {
currentValue = operatorMethod.bind(targetValue).call(this, [
indexValue,
], {});
} on ReturnException catch (e) {
currentValue = e.value;
} catch (e) {
throw RuntimeD4rtException(
"Error executing class operator '[]' for compound read: $e",
);
}
} else {
// No class operator found, try extensions
try {
final extensionGetter = environment.findExtensionMember(
targetValue,
'[]',
);
if (extensionGetter is ExtensionMemberCallable &&
extensionGetter.isOperator) {
final extensionPositionalArgs = [targetValue, indexValue];
try {
currentValue = extensionGetter.call(
this,
extensionPositionalArgs,
{},
);
} on ReturnException catch (e) {
currentValue = e.value;
} catch (e) {
throw RuntimeD4rtException(
"Error executing extension operator '[]' for compound read: $e",
);
}
} else {
throw RuntimeD4rtException(
'Cannot read current value for compound index assignment on ${targetValue.klass.name}: No operator [] found (class or extension).',
);
}
} on RuntimeD4rtException catch (e) {
throw RuntimeD4rtException(
'Cannot read current value for compound index assignment on ${targetValue.klass.name}: ${e.message}',
);
}
}
} else if (toBridgedInstance(targetValue).$2) {
// Handle BridgedInstance for reading current value via [] operator
final bridgedInstance = toBridgedInstance(targetValue).$1!;
final bridgedClass = bridgedInstance.bridgedClass;
final operatorName = '[]';
final methodAdapter = bridgedClass.findInstanceMethodAdapter(
operatorName,
);
if (methodAdapter != null) {
Logger.debug(
"[visitAssignmentExpression-Index] Found bridged operator '$operatorName' for ${bridgedClass.name}. Calling adapter for compound read...",
);
try {
currentValue = methodAdapter(
this,
bridgedInstance.nativeObject,
[indexValue],
{},
null,
);
} catch (e, s) {
Logger.error(
"[visitAssignmentExpression-Index] Native exception during bridged operator '$operatorName' read on ${bridgedClass.name}: $e\\n$s",
);
throw RuntimeD4rtException(
"Native error during bridged operator '$operatorName' read on ${bridgedClass.name}: $e",
originalException: e,
);
}
} else {
throw RuntimeD4rtException(
'Cannot read current value for compound index assignment on ${bridgedClass.name}: No bridged operator [] found.',
);
}
} else {
try {
final extensionGetter = environment.findExtensionMember(
targetValue,
'[]',
);
if (extensionGetter is ExtensionMemberCallable &&
extensionGetter.isOperator) {
final extensionPositionalArgs = [targetValue, indexValue];
try {
currentValue = extensionGetter.call(
this,
extensionPositionalArgs,
{},
);
} on ReturnException catch (e) {
currentValue = e.value;
} // Handle potential returns
catch (e) {
throw RuntimeD4rtException(
"Error executing extension operator '[]' for compound read: $e",
);
}
} else {
throw RuntimeD4rtException(
'Cannot read current value for compound index assignment on type ${targetValue?.runtimeType}: No standard or extension operator [] found.',
);
}
} on RuntimeD4rtException catch (e) {
throw RuntimeD4rtException(
'Cannot read current value for compound index assignment on type ${targetValue?.runtimeType}: ${e.message}',
);
}
}
// 2. Calculate the new value
finalValueToAssign = computeCompoundValue(
currentValue,
rhsValue,
operatorType,
);
}
// Now, perform the assignment with finalValueToAssign
if (targetValue is Map) {
targetValue[indexValue] = finalValueToAssign;
return finalValueToAssign;
} else if (targetValue is List && indexValue is int) {
if (indexValue < 0 || indexValue >= targetValue.length) {
throw RuntimeD4rtException(
'Index out of range for assignment: $indexValue',
);
}
targetValue[indexValue] = finalValueToAssign;
return finalValueToAssign;
} else if (targetValue is InterpretedInstance) {
// Check for class operator []= method
final operatorMethod = targetValue.findOperator('[]=');
if (operatorMethod != null) {
Logger.debug(
"[visitAssignmentExpression-Index] Found class operator '[]=' on ${targetValue.klass.name}. Calling...",
);
try {
operatorMethod.bind(targetValue).call(this, [
indexValue,
finalValueToAssign,
], {});
return finalValueToAssign;
} on ReturnException catch (_) {
return finalValueToAssign; // []= should not return a value, but assignment expression returns assigned value
} catch (e) {
throw RuntimeD4rtException(
"Error executing class operator '[]=': $e",
);
}
} else {
// No class operator found, try extensions
const operatorName = '[]=';
try {
final extensionSetter = environment.findExtensionMember(
targetValue,
operatorName,
);
if (extensionSetter is ExtensionMemberCallable &&
extensionSetter.isOperator) {
Logger.debug(
"[Assignment] Found extension operator '[]=' for ${targetValue.klass.name}. Calling...",
);
// Args: receiver (targetValue), index (indexValue), value (finalValueToAssign)
final extensionPositionalArgs = [
targetValue,
indexValue,
finalValueToAssign,
];
try {
extensionSetter.call(this, extensionPositionalArgs, {});
// '[]=' operator should not return a meaningful value, but the assignment expression returns the assigned value
return finalValueToAssign;
} catch (e) {
throw RuntimeD4rtException(
"Error executing extension operator '[]=': $e",
);
}
} else {
throw RuntimeD4rtException(
'Cannot assign to index on ${targetValue.klass.name}: No operator []= found (class or extension).',
);
}
} on RuntimeD4rtException catch (findError) {
throw RuntimeD4rtException(
'Cannot assign to index on ${targetValue.klass.name}: ${findError.message}',
);
}
}
} else if (toBridgedInstance(targetValue).$2) {
final bridgedInstance = toBridgedInstance(targetValue).$1!;
final bridgedClass = bridgedInstance.bridgedClass;
final operatorName = '[]=';
final methodAdapter = bridgedClass.findInstanceMethodAdapter(
operatorName,
);
if (methodAdapter != null) {
Logger.debug(
"[visitAssignmentExpression-Index] Found bridged operator '$operatorName' for ${bridgedClass.name}. Calling adapter...",
);
try {
methodAdapter(
this,
bridgedInstance.nativeObject,
[indexValue, finalValueToAssign],
{},
null,
);
return finalValueToAssign;
} catch (e, s) {
Logger.error(
"[visitAssignmentExpression-Index] Native exception during bridged operator '$operatorName' on ${bridgedClass.name}: $e\\n$s",
);
throw RuntimeD4rtException(
"Native error during bridged operator '$operatorName' on ${bridgedClass.name}: $e",
originalException: e,
);
}
}
throw RuntimeD4rtException(
"[Bridged operator '$operatorName' not found directly for ${bridgedClass.name}. Trying extensions.",
);
} else {
const operatorName = '[]=';
try {
final extensionSetter = environment.findExtensionMember(
targetValue,
operatorName,
);
if (extensionSetter is ExtensionMemberCallable &&
extensionSetter.isOperator) {
Logger.debug(
"[Assignment] Found extension operator '[]=' for type ${targetValue?.runtimeType}. Calling...",
);
// Args: receiver (targetValue), index (indexValue), value (finalValueToAssign)
final extensionPositionalArgs = [
targetValue,
indexValue,
finalValueToAssign,
];
try {
extensionSetter.call(this, extensionPositionalArgs, {});
// '[]=' operator should not return a meaningful value, but the assignment expression returns the assigned value
return finalValueToAssign;
} catch (e) {
throw RuntimeD4rtException(
"Error executing extension operator '[]=': $e",
);
}
} // else: No suitable extension operator found, fall through
Logger.debug(
"[Assignment] No suitable extension operator '[]=' found for type ${targetValue?.runtimeType}.",
);
} on RuntimeD4rtException catch (findError) {
Logger.debug(
"[Assignment] No extension member '[]=' found for type ${targetValue?.runtimeType}. Error: ${findError.message}",
);
// Fall through to the final error
}
// If neither standard nor extension assignment worked
throw RuntimeD4rtException(
'Unsupported target for index assignment: ${targetValue?.runtimeType}',
);
}
} else {
throw UnimplementedD4rtException(
'Assignation à une cible non gérée: ${lhs.runtimeType}',
);
}
}
}