visitPostfixExpression method
Visit a SPostfixExpression.
Implementation
@override
Object? visitPostfixExpression(SPostfixExpression node) {
final operatorType = node.operator;
// Support for the non-null assertion operator (!)
if (operatorType == '!') {
final operandValue = node.operand!.accept<Object?>(this);
if (operandValue == null) {
throw RuntimeD4rtException(
"Null check operator used on a null value at ${node.toString()}",
);
}
return operandValue;
}
// Check if operand is assignable (SSimpleIdentifier or SPropertyAccess)
if (node.operand is SSimpleIdentifier) {
final variableName = (node.operand as SSimpleIdentifier).name;
Object? operandValue;
InterpretedInstance? thisInstance;
bool isInstanceField = false;
// Try lexical scope first, then implicit 'this'
try {
operandValue = environment.get(variableName); // Try lexical scope
} on RuntimeD4rtException {
// Not found lexically, try implicit 'this'
try {
final potentialThis = environment.get('this');
if (potentialThis is InterpretedInstance) {
thisInstance = potentialThis;
operandValue = thisInstance.get(variableName); // Get from instance
isInstanceField = true;
} else {
throw RuntimeD4rtException("Undefined variable: $variableName");
}
} on LateInitializationError {
// Plan H: surface unwrapped — matches native Dart behaviour.
rethrow;
} on RuntimeD4rtException {
throw RuntimeD4rtException("Undefined variable: $variableName");
}
}
final bridgedInstance = toBridgedInstance(operandValue);
final currentValue = bridgedInstance.$2
? bridgedInstance.$1!.nativeObject
: operandValue;
if (currentValue is num) {
final newValue = operatorType == '++'
? currentValue + 1
: currentValue - 1;
// Assign back to correct target (lexical or instance)
if (isInstanceField && thisInstance != null) {
final setter = thisInstance.klass.findInstanceSetter(variableName);
if (setter != null) {
setter.bind(thisInstance).call(this, [newValue], {});
} else {
thisInstance.set(
variableName,
newValue,
this,
); // Assign to instance field
}
} else {
environment.assign(
variableName,
newValue,
); // Assign to lexical variable
}
return operandValue; // Return the original value
} else if (operandValue is InterpretedInstance) {
// Use custom + operator with literal 1
final operatorMethod = operandValue.findOperator('+');
if (operatorMethod != null) {
try {
// For x++, we create a literal 1 and call x + 1
final operand = _createIncrementOperand(
currentValue,
operatorType == '++',
);
Object? newValue = operatorMethod.bind(operandValue).call(this, [
operand,
], {});
// Assign back to correct target (lexical or instance)
if (isInstanceField && thisInstance != null) {
final setter = thisInstance.klass.findInstanceSetter(
variableName,
);
if (setter != null) {
setter.bind(thisInstance).call(this, [newValue], {});
} else {
thisInstance.set(
variableName,
newValue,
this,
); // Assign to instance field
}
} else {
environment.assign(
variableName,
newValue,
); // Assign to lexical variable
}
return operandValue; // Return the original value for postfix
} on ReturnException catch (e) {
final newValue = e.value;
// Assign back to correct target (lexical or instance)
if (isInstanceField && thisInstance != null) {
final setter = thisInstance.klass.findInstanceSetter(
variableName,
);
if (setter != null) {
setter.bind(thisInstance).call(this, [newValue], {});
} else {
thisInstance.set(
variableName,
newValue,
this,
); // Assign to instance field
}
} else {
environment.assign(
variableName,
newValue,
); // Assign to lexical variable
}
return operandValue; // Return the original value for postfix
} catch (e) {
throw RuntimeD4rtException(
"Error executing custom operator '+' for postfix '${operatorType == '++' ? '++' : '--'}': $e",
);
}
} else {
throw RuntimeD4rtException(
"Cannot increment/decrement object of type '${operandValue.klass.name}': No operator '+' found.",
);
}
} else {
throw RuntimeD4rtException(
"Operand for postfix '${operatorType == '++' ? '++' : '--'}' must be a number, but was ${operandValue?.runtimeType}.",
);
}
} else if (node.operand is SPropertyAccess) {
// Handle property access like obj.field++
final propertyAccess = node.operand as SPropertyAccess;
final targetValue = propertyAccess.target?.accept<Object?>(this);
final propertyName = propertyAccess.propertyName!.name;
if (targetValue is InterpretedInstance) {
// Get current value via getter or field
final currentValue = targetValue.get(propertyName);
final originalValue = currentValue; // Save for return
// Calculate new value
Object? newValue;
if (currentValue is num) {
newValue = operatorType == '++' ? currentValue + 1 : currentValue - 1;
} else if (currentValue is InterpretedInstance) {
// Use custom + operator with literal 1
final operatorMethod = currentValue.findOperator('+');
if (operatorMethod != null) {
try {
// For x++, we create a literal 1 and call x + 1
final operand = _createIncrementOperand(
currentValue,
operatorType == '++',
);
newValue = operatorMethod.bind(currentValue).call(this, [
operand,
], {});
} on ReturnException catch (e) {
newValue = e.value;
} catch (e) {
throw RuntimeD4rtException(
"Error executing custom operator '+' for postfix '${operatorType == '++' ? '++' : '--'}': $e",
);
}
} else {
throw RuntimeD4rtException(
"Cannot increment/decrement object of type '${currentValue.klass.name}': No operator '+' found.",
);
}
} else {
throw RuntimeD4rtException(
"Cannot increment/decrement property '$propertyName' of type '${currentValue?.runtimeType}': Expected number or object with '+' operator.",
);
}
// Set new value via setter or field
final setter = targetValue.klass.findInstanceSetter(propertyName);
if (setter != null) {
setter.bind(targetValue).call(this, [newValue], {});
} else {
targetValue.set(propertyName, newValue, this);
}
// Return the *original* value for postfix operators
return originalValue;
} else {
throw RuntimeD4rtException(
"Cannot increment/decrement property on non-instance object of type '${targetValue?.runtimeType}'.",
);
}
} else if (node.operand is SPrefixedIdentifier) {
// Handle prefixed identifier like obj.field++ (parsed as SPrefixedIdentifier)
final prefixedIdentifier = node.operand as SPrefixedIdentifier;
final targetValue = prefixedIdentifier.prefix!.accept<Object?>(this);
final propertyName = prefixedIdentifier.identifier!.name;
if (targetValue is InterpretedInstance) {
// Get current value via getter or field
final currentValue = targetValue.get(propertyName);
final originalValue = currentValue; // Save for return
// Calculate new value
Object? newValue;
if (currentValue is num) {
newValue = operatorType == '++' ? currentValue + 1 : currentValue - 1;
} else if (currentValue is InterpretedInstance) {
// Use custom + operator with literal 1
final operatorMethod = currentValue.findOperator('+');
if (operatorMethod != null) {
try {
// For x++, we create a literal 1 and call x + 1
final operand = _createIncrementOperand(
currentValue,
operatorType == '++',
);
newValue = operatorMethod.bind(currentValue).call(this, [
operand,
], {});
} on ReturnException catch (e) {
newValue = e.value;
} catch (e) {
throw RuntimeD4rtException(
"Error executing custom operator '+' for postfix '${operatorType == '++' ? '++' : '--'}': $e",
);
}
} else {
throw RuntimeD4rtException(
"Cannot increment/decrement object of type '${currentValue.klass.name}': No operator '+' found.",
);
}
} else {
throw RuntimeD4rtException(
"Cannot increment/decrement property '$propertyName' of type '${currentValue?.runtimeType}': Expected number or object with '+' operator.",
);
}
// Set new value via setter or field
final setter = targetValue.klass.findInstanceSetter(propertyName);
if (setter != null) {
setter.bind(targetValue).call(this, [newValue], {});
} else {
targetValue.set(propertyName, newValue, this);
}
// Return the *original* value for postfix operators
return originalValue;
} else if (targetValue is InterpretedExtension) {
// Handle static field/getter increment/decrement on extension
final extension = targetValue;
// Get current value via static getter or field
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(
"Extension '${extension.name}' has no static field or getter named '$propertyName'.",
);
}
final originalValue = currentValue; // Save for return
// Calculate new value
Object? newValue;
if (currentValue is num) {
newValue = operatorType == '++' ? currentValue + 1 : currentValue - 1;
} else {
throw RuntimeD4rtException(
"Cannot increment/decrement static property '$propertyName' of type '${currentValue?.runtimeType}': Expected number.",
);
}
// Set new value via static setter or field
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(
"Extension '${extension.name}' has no static setter or field named '$propertyName'.",
);
}
// Return the *original* value for postfix operators
return originalValue;
} else {
throw RuntimeD4rtException(
"Cannot increment/decrement property on non-instance object of type '${targetValue?.runtimeType}'.",
);
}
} else if (node.operand is SIndexExpression) {
// Handle index access like array[i]++
final indexExpression = node.operand as SIndexExpression;
final targetValue = indexExpression.target?.accept<Object?>(this);
final indexValue = indexExpression.index!.accept<Object?>(this);
// Get current value via [] operator or direct access
Object? currentValue;
if (targetValue is List) {
final index = indexValue as int;
currentValue = targetValue[index];
} else if (targetValue is Map) {
currentValue = targetValue[indexValue];
} else if (targetValue is InterpretedInstance) {
// Use class operator [] if available
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 postfix '${operatorType == '++' ? '++' : '--'}': $e",
);
}
} else {
throw RuntimeD4rtException(
"Cannot read index for postfix increment/decrement on ${targetValue.klass.name}: No operator '[]' found.",
);
}
} else {
throw RuntimeD4rtException(
"Cannot apply postfix '${operatorType == '++' ? '++' : '--'}' to index of type '${targetValue?.runtimeType}'.",
);
}
final originalValue = currentValue; // Save for return
// Calculate new value
Object? newValue;
if (currentValue is num) {
newValue = operatorType == '++' ? currentValue + 1 : currentValue - 1;
} else if (currentValue is InterpretedInstance) {
// Use custom + operator with literal 1
final operatorMethod = currentValue.findOperator('+');
if (operatorMethod != null) {
try {
final operand = _createIncrementOperand(
currentValue,
operatorType == '++',
);
newValue = operatorMethod.bind(currentValue).call(this, [
operand,
], {});
} on ReturnException catch (e) {
newValue = e.value;
} catch (e) {
throw RuntimeD4rtException(
"Error executing custom operator '+' for postfix '${operatorType == '++' ? '++' : '--'}': $e",
);
}
} else {
throw RuntimeD4rtException(
"Cannot increment/decrement object at index of type '${currentValue.klass.name}': No operator '+' found.",
);
}
} else {
throw RuntimeD4rtException(
"Cannot increment/decrement value at index of type '${currentValue?.runtimeType}': Expected number or object with '+' operator.",
);
}
// Set new value via []= operator or direct access
if (targetValue is List) {
final index = indexValue as int;
targetValue[index] = newValue;
} else if (targetValue is Map) {
targetValue[indexValue] = newValue;
} else if (targetValue is InterpretedInstance) {
// Use class operator []= if available
final operatorMethod = targetValue.findOperator('[]=');
if (operatorMethod != null) {
try {
operatorMethod.bind(targetValue).call(this, [
indexValue,
newValue,
], {});
} on ReturnException catch (_) {
// []= should not return a value, but assignment expression returns assigned value
} catch (e) {
throw RuntimeD4rtException(
"Error executing class operator '[]=' for postfix '${operatorType == '++' ? '++' : '--'}': $e",
);
}
} else {
throw RuntimeD4rtException(
"Cannot write index for postfix increment/decrement on ${targetValue.klass.name}: No operator '[]=' found.",
);
}
}
// Return the *original* value for postfix operators
return originalValue;
} else {
throw RuntimeD4rtException(
"Operand for postfix '${operatorType == '++' ? '++' : '--'}' must be an assignable variable or property.",
);
}
}