visitMethodInvocation method
Visit a SMethodInvocation.
Implementation
@override
Object? visitMethodInvocation(SMethodInvocation node) {
Object? calleeValue;
Object? targetValue; // Keep track of the target object/class
// Argument lists - declared here, evaluated later if needed
// Determine if this is a conditional call by checking the operator field
final isNullAware = node.operator == '?.';
if (node.target == null) {
// Simple function call (or class constructor call)
calleeValue = node.methodName!.accept<Object?>(this);
targetValue = null; // No target
} else {
// Property/Method call on a target (instance or class)
targetValue = node.target!.accept<Object?>(this);
final methodName = node.methodName!.name;
// Null safety support: if the target is null and the call is null-aware, return null
if (targetValue == null) {
if (isNullAware) {
return null;
}
// C21 — Dart null-shorting: when an inner selector in this chain
// uses `?.` (e.g. `a?.b.c()` where `a == null`), the outer `.c()`
// must short-circuit to null instead of throwing. The chain
// terminates at parentheses or non-selector expressions.
if (_chainHasNullAwareSelector(node.target)) {
return null;
}
throw RuntimeD4rtException(
"Cannot invoke method '$methodName' on null. Use '?.' for null-aware method invocation.",
);
}
// BLOCK FOR HANDLING PREFIXED IMPORTS
if (targetValue is Environment) {
Logger.debug(
"[SMethodInvocation] Target is an Environment (prefixed import '${node.target!.toString()}'). Looking for method '$methodName' in this environment.",
);
try {
calleeValue = targetValue.get(methodName);
// The 'targetValue' for the call will be null because this is not an instance method on the environment itself,
// but a function retrieved from this environment.
// Functions obtained in this way are already "autonomous" or correctly bound if they come from classes.
} on RuntimeD4rtException catch (e) {
final moduleName = node.target is SSimpleIdentifier
? (node.target as SSimpleIdentifier).name
: node.target.toString();
throw RuntimeD4rtException(
"Method '$methodName' not found in imported module '$moduleName'. Error: ${e.message}",
);
}
// calleeValue is now the function/method of the imported module.
// The call logic will be handled in the final visitMethodInvocation.
} else if (targetValue is InterpretedInstance) {
// Instance method call
try {
// Get should return the BOUND method
calleeValue = targetValue.get(methodName);
Logger.debug(
"[SMethodInvocation] Found direct instance member '$methodName' on ${targetValue.klass.name}. Type: ${calleeValue?.runtimeType}",
);
} on RuntimeD4rtException catch (e) {
if (e.message.contains("Undefined property '$methodName'")) {
Logger.debug(
"[SMethodInvocation] Direct instance method '$methodName' failed/not found on ${targetValue.klass.name}. Error: ${e.message}. Trying extension method...",
);
try {
final extensionCallable = environment.findExtensionMember(
targetValue,
methodName,
);
if (extensionCallable is ExtensionMemberCallable &&
!extensionCallable
.isOperator && // Ensure it's a regular method
!extensionCallable.isGetter &&
!extensionCallable.isSetter) {
Logger.debug(
"[SMethodInvocation] Found extension method '$methodName'. Evaluating args and calling...",
);
// Evaluate arguments (must be done here as direct call failed)
final evaluationResult = _evaluateArgumentsAsync(
node.argumentList,
);
if (evaluationResult is AsyncSuspensionRequest) {
return evaluationResult; // Propagate suspension
}
final (positionalArgs, namedArgs) =
evaluationResult as (List<Object?>, Map<String, Object?>);
List<RuntimeType>? evaluatedTypeArguments;
final typeArgsNode = node.typeArguments;
if (typeArgsNode != null) {
evaluatedTypeArguments = typeArgsNode.arguments
.map((typeNode) => _resolveTypeAnnotation(typeNode))
.toList();
}
// Prepare arguments for extension method:
// First arg is the receiver (the target instance)
final extensionPositionalArgs = [
targetValue,
...positionalArgs,
];
// Call the extension method
try {
// Return the result of the extension call directly
return extensionCallable.call(
this,
extensionPositionalArgs,
namedArgs,
evaluatedTypeArguments,
);
} on ReturnException catch (returnExc) {
return returnExc.value;
} catch (execError) {
throw RuntimeD4rtException(
"Error executing extension method '$methodName': $execError",
);
}
} else {
// No suitable extension found - check for noSuchMethod
Logger.debug(
"[SMethodInvocation] Extension method '$methodName' not found or not applicable. Checking for noSuchMethod...",
);
// Bug-78 FIX: Check for noSuchMethod before throwing error
final noSuchMethod = targetValue.klass.findInstanceMethod(
'noSuchMethod',
);
if (noSuchMethod != null) {
Logger.debug(
"[SMethodInvocation] Found noSuchMethod on ${targetValue.klass.name}. Invoking...",
);
// Evaluate arguments for the noSuchMethod call
final evaluationResult = _evaluateArgumentsAsync(
node.argumentList,
);
if (evaluationResult is AsyncSuspensionRequest) {
return evaluationResult;
}
final (positionalArgs, namedArgs) =
evaluationResult as (List<Object?>, Map<String, Object?>);
// Create an Invocation.method for this method call
final namedArgsSymbol = namedArgs.map(
(key, value) => MapEntry(Symbol(key), value),
);
final invocation = Invocation.method(
Symbol(methodName),
positionalArgs,
namedArgsSymbol,
);
final boundNoSuchMethod = noSuchMethod.bind(targetValue);
try {
return boundNoSuchMethod.call(this, [invocation], {});
} on ReturnException catch (returnExc) {
return returnExc.value;
}
}
throw RuntimeD4rtException(
"Instance of '${targetValue.klass.name}' has no method named '$methodName' and no suitable extension method found. Original error: (${e.message})",
);
}
} on RuntimeD4rtException catch (findError) {
// Error during the findExtensionMember call itself
Logger.debug(
"[SMethodInvocation] Error during extension lookup for '$methodName': ${findError.message}. Checking for noSuchMethod...",
);
// Bug-78 FIX: Check for noSuchMethod before throwing error
final noSuchMethod = targetValue.klass.findInstanceMethod(
'noSuchMethod',
);
if (noSuchMethod != null) {
Logger.debug(
"[SMethodInvocation] Found noSuchMethod on ${targetValue.klass.name}. Invoking...",
);
// Evaluate arguments for the noSuchMethod call
final evaluationResult = _evaluateArgumentsAsync(
node.argumentList,
);
if (evaluationResult is AsyncSuspensionRequest) {
return evaluationResult;
}
final (positionalArgs, namedArgs) =
evaluationResult as (List<Object?>, Map<String, Object?>);
// Create an Invocation.method for this method call
final namedArgsSymbol = namedArgs.map(
(key, value) => MapEntry(Symbol(key), value),
);
final invocation = Invocation.method(
Symbol(methodName),
positionalArgs,
namedArgsSymbol,
);
final boundNoSuchMethod = noSuchMethod.bind(targetValue);
try {
return boundNoSuchMethod.call(this, [invocation], {});
} on ReturnException catch (returnExc) {
return returnExc.value;
}
}
throw RuntimeD4rtException(
"Instance of '${targetValue.klass.name}' has no method named '$methodName'. Error during extension lookup: ${findError.message}. Original error: (${e.message})",
);
}
} else {
// The error during direct get() wasn't "Undefined property", rethrow it
rethrow;
}
}
// We check if it's callable later (if direct lookup succeeded)
} else if (targetValue is InterpretedEnumValue) {
try {
// Get should return the BOUND method (or execute getter)
// Pass the visitor to potentially execute getters
calleeValue = targetValue.get(methodName, this);
Logger.debug(
"[SMethodInvocation] Found enum instance member '$methodName' on $targetValue. Type: ${calleeValue?.runtimeType}",
);
} on RuntimeD4rtException catch (e) {
// Try Extension Method if Direct Fails (similar to InterpretedInstance)
if (e.message.contains("Undefined property '$methodName'")) {
Logger.debug(
"[SMethodInvocation] Direct enum method '$methodName' failed/not found on $targetValue. Error: ${e.message}. Trying extension method...",
);
try {
final extensionCallable = environment.findExtensionMember(
targetValue,
methodName,
);
if (extensionCallable is ExtensionMemberCallable &&
!extensionCallable.isOperator &&
!extensionCallable.isGetter &&
!extensionCallable.isSetter) {
Logger.debug(
"[SMethodInvocation] Found extension method '$methodName' for enum value. Evaluating args and calling...",
);
final evaluationResult = _evaluateArgumentsAsync(
node.argumentList,
);
if (evaluationResult is AsyncSuspensionRequest) {
return evaluationResult; // Propagate suspension
}
final (positionalArgs, namedArgs) =
evaluationResult as (List<Object?>, Map<String, Object?>);
List<RuntimeType>?
evaluatedTypeArguments; // Handle type args if needed
final extensionPositionalArgs = [
targetValue,
...positionalArgs,
];
try {
return extensionCallable.call(
this,
extensionPositionalArgs,
namedArgs,
evaluatedTypeArguments,
);
} on ReturnException catch (returnExc) {
return returnExc.value;
} catch (execError) {
throw RuntimeD4rtException(
"Error executing extension method '$methodName' on enum value: $execError",
);
}
} else {
if (methodName == 'toString') {
return targetValue.toString();
} else if (methodName == 'runtimeType') {
return targetValue.runtimeType;
}
Logger.debug(
"[SMethodInvocation] Extension method '$methodName' for enum value not found or not applicable. Rethrowing original error.",
);
throw RuntimeD4rtException(
"Enum value '$targetValue' has no method named '$methodName' and no suitable extension method found. Original error: (${e.message})",
);
}
} on RuntimeD4rtException catch (findError) {
Logger.debug(
"[SMethodInvocation] Error during extension lookup for '$methodName' on enum value: ${findError.message}. Rethrowing original error.",
);
throw RuntimeD4rtException(
"Enum value '$targetValue' has no method named '$methodName'. Error during extension lookup: ${findError.message}. Original error: (${e.message})",
);
}
} else {
rethrow; // Rethrow other errors from get()
}
}
} else if (toBridgedInstance(targetValue).$2) {
final bridgedInstance = toBridgedInstance(targetValue).$1!;
final bridgedClass = bridgedInstance.bridgedClass;
switch (methodName) {
case 'toString':
return targetValue.toString();
default:
}
// Use directly methods because we need the BridgedMethodCallable
final adapter = bridgedClass.methods[methodName];
if (adapter != null) {
final evaluationResult = _evaluateArgumentsAsync(node.argumentList);
if (evaluationResult is AsyncSuspensionRequest) {
return evaluationResult; // Propagate suspension
}
final (positionalArgs, namedArgs) =
evaluationResult as (List<Object?>, Map<String, Object?>);
// Plan E: forward script-supplied generic type arguments to the
// bridged method adapter so interceptors (e.g. for
// `Element.dependOnInheritedWidgetOfExactType<T>`) can act on `T`.
List<RuntimeType>? evaluatedTypeArguments;
final typeArgsNode = node.typeArguments;
if (typeArgsNode != null) {
evaluatedTypeArguments = typeArgsNode.arguments
.map((typeNode) => _resolveTypeAnnotation(typeNode))
.toList();
}
try {
// C6b fix (mirror of tom_d4rt/interpreter_visitor.dart): wrap
// with `D4.withActiveVisitor` so visitor-less helpers invoked
// inside the adapter (e.g. `D4.coerceList<T>` resolving
// InterpretedInstance values via registered interface proxies
// for higher-kinded generics like
// `ThemeExtension<ThemeExtension<dynamic>>`) can read
// `_activeVisitor`.
return D4.withActiveVisitor(
this,
() => adapter(
this,
bridgedInstance.nativeObject,
positionalArgs,
namedArgs,
evaluatedTypeArguments,
),
);
} on ReturnException catch (e) {
// Native calls shouldn't throw ReturnException directly, but handle defensively
return e.value;
} catch (e, s) {
// 1944 TODO A.8 (2026-05-31): mirror of tom_d4rt removal.
// The historical Cluster B item #4+#5 `findRenderObject` /
// `'Cannot get renderObject of inactive element'` catch
// that returned `null` has been REMOVED. Discovery sweep
// across both projects' full corpora found ZERO
// `[A8_CATCH_FIRED]` hits — the catch was dead code in
// the current script-set. See `interpreter_unfixable.md`
// §U27 for the architectural framing and the rationale
// for restoring this narrow null-return catch if a future
// script regresses on the same pattern.
// Add the stack trace for debugging
Logger.log("Native Error Stack Trace: $s"); // Print stack trace
// Catch potential errors from the native code/adapter
throw RuntimeD4rtException(
"Native error during bridged method call '$methodName' on ${bridgedClass.name}: $e",
originalException: e,
);
}
} else {
// C13 follow-up: before falling back to extensions, try a getter
// with the same name. Dart allows `obj.foo()` where `foo` is a
// getter returning a callable value (e.g. dart:foundation
// Factory<T>.constructor returns ValueGetter<T>); the call-site
// expects getter+invoke semantics, not method-not-found.
final getterAdapter = bridgedClass.findInstanceGetterAdapter(
methodName,
);
if (getterAdapter != null) {
final getterValue = D4.withActiveVisitor<Object?>(
this,
() => getterAdapter(this, bridgedInstance.nativeObject),
);
if (getterValue is Callable || getterValue is Function) {
final evaluationResult = _evaluateArgumentsAsync(
node.argumentList,
);
if (evaluationResult is AsyncSuspensionRequest) {
return evaluationResult;
}
final (positionalArgs, namedArgs) =
evaluationResult as (List<Object?>, Map<String, Object?>);
if (getterValue is Callable) {
return getterValue.call(this, positionalArgs, namedArgs);
}
final symbolNamed = namedArgs.isEmpty
? const <Symbol, Object?>{}
: namedArgs
.map<Symbol, Object?>((k, v) => MapEntry(Symbol(k), v));
try {
return Function.apply(
getterValue as Function,
positionalArgs,
symbolNamed,
);
} on ReturnException catch (e) {
return e.value;
}
}
// Getter returned a non-callable — fall through to the
// "not a method" error path below for a clear message.
}
// D2 sync: If the bridged target wraps a [D4InterpretedProxy]
// (a native proxy that holds a back-reference to the
// originating [InterpretedInstance]), retry the method lookup
// on the wrapped instance. Mirrors the analyzer-side fix in
// tom_d4rt for the MethodInvocation bridged-instance path.
// Used when a script-defined State subclass is reached via
// `GlobalKey.currentState` (which returns the native
// `_InterpretedState` proxy) and the call-site invokes a
// user-defined method like `flyOff(dir)` — without this
// unwrap, the interpreter only sees the bridged State
// methods and throws "no instance method named X". Generic:
// applies to every D4InterpretedProxy (CustomPainter, State,
// SingleTickerProviderState, …).
final nativeTarget = bridgedInstance.nativeObject;
if (nativeTarget is D4InterpretedProxy) {
final inner = nativeTarget.d4rtInstance;
if (inner is InterpretedInstance) {
try {
final bound = inner.get(methodName);
if (bound is Callable) {
final evaluationResult = _evaluateArgumentsAsync(
node.argumentList,
);
if (evaluationResult is AsyncSuspensionRequest) {
return evaluationResult;
}
final (positionalArgs, namedArgs) = evaluationResult
as (List<Object?>, Map<String, Object?>);
List<RuntimeType>? evaluatedTypeArguments;
final typeArgsNode = node.typeArguments;
if (typeArgsNode != null) {
evaluatedTypeArguments = typeArgsNode.arguments
.map((typeNode) => _resolveTypeAnnotation(typeNode))
.toList();
}
return bound.call(
this,
positionalArgs,
namedArgs,
evaluatedTypeArguments,
);
}
} on RuntimeD4rtException {
// Method not found on the inner interpreted instance —
// fall through to extension lookup / not-found error.
}
}
}
// No adapter found for this method name, try extension methods
Logger.debug(
"[visitMethodInvocation] Bridged method '$methodName' not found directly for ${bridgedClass.name}. Trying extensions.",
);
try {
final extensionMethod = environment.findExtensionMember(
targetValue,
methodName,
);
if (extensionMethod is ExtensionMemberCallable) {
Logger.debug(
"[visitMethodInvocation] Found extension method '$methodName' for ${bridgedClass.name}. Calling...",
);
final evaluationResult = _evaluateArgumentsAsync(
node.argumentList,
);
if (evaluationResult is AsyncSuspensionRequest) {
return evaluationResult; // Propagate suspension
}
final (positionalArgs, namedArgs) =
evaluationResult as (List<Object?>, Map<String, Object?>);
final extensionArgs = <Object?>[targetValue];
extensionArgs.addAll(positionalArgs);
return extensionMethod.call(this, extensionArgs, namedArgs);
} else {
throw RuntimeD4rtException(
"Bridged class '${bridgedClass.name}' has no instance method named '$methodName'.",
);
}
} on RuntimeD4rtException catch (findError) {
throw RuntimeD4rtException(
"Bridged class '${bridgedClass.name}' has no instance method named '$methodName'. Error during extension lookup: ${findError.message}",
);
}
}
// Note: This block returns directly or throws, it does not set calleeValue.
}
// Handle static method call OR NAMED CONSTRUCTOR call
else if (targetValue is InterpretedClass) {
// Check for NAMED CONSTRUCTOR first
final namedConstructor = targetValue.findConstructor(methodName);
if (namedConstructor != null) {
// It's a named constructor call
// G-DOV2-3 FIX: Check abstract AFTER finding constructor, skip if factory
if (targetValue.isAbstract && !namedConstructor.isFactory) {
throw RuntimeD4rtException(
"Cannot instantiate abstract class '${targetValue.name}'.",
);
}
final evaluationResult = _evaluateArgumentsAsync(node.argumentList);
if (evaluationResult is AsyncSuspensionRequest) {
return evaluationResult; // Propagate suspension
}
final (positionalArgs, namedArgs) =
evaluationResult as (List<Object?>, Map<String, Object?>);
try {
// Handle factory constructors differently from regular constructors
if (namedConstructor.isFactory) {
// Factory constructors should create and return their own instance
// Do NOT create an instance beforehand
Logger.debug(
"[SMethodInvocation] Calling factory constructor '$methodName' directly",
);
final result = namedConstructor.call(
this,
positionalArgs,
namedArgs,
);
return result;
} else {
// Regular constructor: create instance first, then call constructor
// 1. Create and initialize instance fields (using the class's public helper)
// Pass null for type arguments as they aren't applicable to named constructor resolution here
final instance = targetValue.createAndInitializeInstance(
this,
null,
);
// 2. Bind 'this' and call the named constructor logic
final boundConstructor = namedConstructor.bind(instance);
boundConstructor.call(
this,
positionalArgs,
namedArgs,
); // Pass evaluated args
// Constructor call implicitly returns the bound instance.
return instance; // Return the created and potentially modified instance
}
} on ReturnException catch (e) {
return e.value;
} on RuntimeD4rtException catch (e) {
throw RuntimeD4rtException(
"Error during named constructor '$methodName' for class '${targetValue.name}': ${e.message}",
);
}
} else {
// Not a named constructor, check for STATIC METHOD
final staticMethod = targetValue.findStaticMethod(methodName);
if (staticMethod != null) {
calleeValue =
staticMethod; // It's already the function, no binding needed
} else {
// Cluster C33: class-as-value (Type literal) semantics. A script
// that does `final Type t = SomeClass; t.toString();` expects
// `Object.toString()` on the Type instance, which in real Dart
// returns the class name. Fall back here before reporting an
// undefined-method error.
if (methodName == 'toString' &&
(node.argumentList?.arguments.isEmpty ?? true)) {
return targetValue.name;
}
throw RuntimeD4rtException(
"Class '${targetValue.name}' has no static method or named constructor named '$methodName'.",
);
}
}
} else if (targetValue is InterpretedEnum) {
final staticMethod = targetValue.staticMethods[methodName];
if (staticMethod != null) {
calleeValue = staticMethod; // Static method, no binding needed
} else {
// Check mixins for static methods (reverse order)
bool found = false;
for (final mixin in targetValue.mixins.reversed) {
final mixinStaticMethod = mixin.findStaticMethod(methodName);
if (mixinStaticMethod != null) {
calleeValue = mixinStaticMethod;
found = true;
Logger.debug(
"[SMethodInvocation] Found static method '$methodName' from mixin '${mixin.name}' for enum '${targetValue.name}'",
);
break;
}
}
if (!found) {
// Before throwing, let's check if it's a built-in method call like 'values'
// This could potentially be handled by the stdlib call later, but maybe check here?
// For now, assume only user-defined static methods are intended.
throw RuntimeD4rtException(
"Enum '${targetValue.name}' has no static method named '$methodName'.",
);
}
}
} else if (targetValue is InterpretedExtension) {
// Static method call on extension
final extension = targetValue;
final staticMethod = extension.findStaticMethod(methodName);
if (staticMethod != null) {
calleeValue = staticMethod;
Logger.debug(
"[SMethodInvocation] Found static method '$methodName' on extension '${extension.name ?? '<unnamed>'}'",
);
} else {
throw RuntimeD4rtException(
"Extension '${extension.name ?? '<unnamed>'}' has no static method named '$methodName'.",
);
}
} else if (targetValue is BridgedEnumValue) {
// This is a method call on a bridged enum value.
// It must use the invoke() method of BridgedEnumValue.
final evaluationResult = _evaluateArgumentsAsync(node.argumentList);
if (evaluationResult is AsyncSuspensionRequest) {
return evaluationResult; // Propagate suspension
}
final (positionalArgs, namedArgs) =
evaluationResult as (List<Object?>, Map<String, Object?>);
try {
return targetValue.invoke(
this,
methodName,
positionalArgs,
namedArgs,
);
} on RuntimeD4rtException {
// Relaunch the RuntimeErrors directly
rethrow;
} catch (e, s) {
// Catch other potential errors (ex: from the adapter)
Logger.error(
"[visitMethodInvocation] Native exception during bridged enum method call '$targetValue.$methodName': $e\n$s",
);
throw RuntimeD4rtException(
"Native error during bridged enum method call '$methodName' on $targetValue: $e",
originalException: e,
);
}
} else if (targetValue is BridgedClass) {
// This is a method call on a bridged class (bridged constructor or static method)
final bridgedClass = targetValue;
final methodName = node.methodName!.name;
Logger.debug(
"[visitMethodInvocation] Target is BridgedClass: '$methodName' on '${bridgedClass.name}'",
);
// 1. Try to find a constructor adapter
final constructorAdapter = bridgedClass.findConstructorAdapter(
methodName,
);
if (constructorAdapter != null) {
Logger.debug(
"[visitMethodInvocation] Found Bridged CONSTRUCTOR adapter for '$methodName'",
);
final evaluationResult = _evaluateArgumentsAsync(node.argumentList);
if (evaluationResult is AsyncSuspensionRequest) {
return evaluationResult; // Propagate suspension
}
final (positionalArgs, namedArgs) =
evaluationResult as (List<Object?>, Map<String, Object?>);
try {
// Wrap with withActiveVisitor so that D4 helper methods can
// access the visitor for interface proxy creation (RC-1).
final nativeObject = D4.withActiveVisitor(
this,
() => constructorAdapter(this, positionalArgs, namedArgs),
);
if (nativeObject == null) {
throw RuntimeD4rtException(
"Bridged constructor adapter for '${bridgedClass.name}.$methodName' returned null unexpectedly.",
);
}
// Don't wrap Futures or Streams - they need to be usable directly
if (nativeObject is Future || nativeObject is Stream) {
Logger.debug(
"[visitMethodInvocation] Returning native ${nativeObject.runtimeType} directly (not wrapping)",
);
return nativeObject;
}
final bridgedInstance = BridgedInstance(bridgedClass, nativeObject);
Logger.debug(
"[visitMethodInvocation] Created BridgedInstance wrapping native: ${nativeObject.runtimeType}",
);
return bridgedInstance; // Retourner l'instance pontée créée
} on RuntimeD4rtException catch (e) {
// Relaunch the adapter error
throw RuntimeD4rtException(
"Error during bridged constructor '$methodName' for class '${bridgedClass.name}': ${e.message}",
);
} catch (e, s) {
// Catch native errors from the adapter/native constructor
Logger.error(
"[visitMethodInvocation] Native exception during bridged constructor '${bridgedClass.name}.$methodName': $e\n$s",
);
throw RuntimeD4rtException(
"Native error during bridged constructor '$methodName' for class '${bridgedClass.name}': $e",
originalException: e,
);
}
} else {
final staticMethodAdapter = bridgedClass.findStaticMethodAdapter(
methodName,
);
if (staticMethodAdapter != null) {
Logger.debug(
"[visitMethodInvocation] Found Bridged STATIC METHOD adapter for '$methodName'",
);
final evaluationResult = _evaluateArgumentsAsync(node.argumentList);
if (evaluationResult is AsyncSuspensionRequest) {
return evaluationResult; // Propagate suspension
}
final (positionalArgs, namedArgs) =
evaluationResult as (List<Object?>, Map<String, Object?>);
// Evaluate type arguments for generic methods
List<RuntimeType>? evaluatedTypeArguments;
final typeArgsNode = node.typeArguments;
if (typeArgsNode != null) {
evaluatedTypeArguments = typeArgsNode.arguments
.map((typeNode) => _resolveTypeAnnotation(typeNode))
.toList();
}
try {
// C4/D5 fix: wrap with withActiveVisitor so D4 helpers
// (e.g. getRequiredNamedArg → extractBridgedArg) can
// resolve the visitor and walk registered interface
// proxies for InterpretedInstance arguments. Without this,
// single-arg Widget parameters on static bridged methods
// like `DefaultTextStyle.merge(child: …)` reject
// script-defined StatelessWidget subclasses because the
// proxy lookup is skipped (visitor is null).
final result = D4.withActiveVisitor(
this,
() => staticMethodAdapter(
this,
positionalArgs,
namedArgs,
evaluatedTypeArguments,
),
);
return result;
} on RuntimeD4rtException catch (e) {
throw RuntimeD4rtException(
"Error during static bridged method call '$methodName' on ${bridgedClass.name}: ${e.message}",
);
} catch (e, s) {
Logger.warn(
"[visitMethodInvocation] Native exception during static bridged method call '${bridgedClass.name}.$methodName': $e\n$s",
);
throw RuntimeD4rtException(
"Native error during static bridged method call '$methodName' on ${bridgedClass.name}: $e",
originalException: e,
);
}
} else {
// Cluster C32: class-as-value (Type literal) semantics. A script
// that does `final Type t = SomeBridgedClass; t.toString();`
// expects `Object.toString()` on the Type instance. Real Dart
// returns the class name. Fall back here before reporting an
// undefined-method error.
if (methodName == 'toString' &&
(node.argumentList?.arguments.isEmpty ?? true)) {
return bridgedClass.name;
}
throw RuntimeD4rtException(
"Bridged class '${bridgedClass.name}' has no constructor or static method named '$methodName'.",
);
}
}
} else if (targetValue is BoundSuper) {
final instance = targetValue.instance;
final startClass = targetValue.startLookupClass;
InterpretedClass? currentClass = startClass;
InterpretedFunction? superMethod;
// Look for the method in the superclass hierarchy
while (currentClass != null) {
final method = currentClass.findInstanceMethod(methodName);
if (method != null) {
superMethod = method;
break;
}
currentClass = currentClass.superclass;
}
if (superMethod != null) {
// Bind the found super method to the original instance ('this')
calleeValue = superMethod.bind(instance);
} else {
throw RuntimeD4rtException(
"Method '$methodName' not found in superclass chain of '${instance.klass.name}'.",
);
}
// Arguments are evaluated below, calleeValue is now the bound super method
} else if (targetValue is BoundBridgedSuper) {
final instance = targetValue.instance; // L'instance 'this' interprétée
final bridgedSuper = targetValue.startLookupClass;
// RC-6: Use nativeProxy as fallback when bridgedSuperObject is null.
// This supports abstract class adapters (like _InterpretedState for State).
final nativeSuperObject =
instance.bridgedSuperObject ?? instance.nativeProxy;
if (nativeSuperObject == null) {
// RC-8: super.<method>() on a bridged superclass that has no
// realised native target (e.g. State without nativeProxy on a
// plain `_InterpretedState` after the narrowed-#82 fix; scripts
// that mix in `AutomaticKeepAliveClientMixin` need to call
// `super.build(context)` for spec compliance even though the
// mixin's native side isn't reachable from the bridge). Return
// null silently — these calls are typically made for side-effects
// the bridge can't observe and the script discards the result.
Logger.debug(
"[visitMethodInvocation] super.$methodName() on bridged super "
"'${bridgedSuper.name}' has no native target — treating as no-op.");
return null;
}
// Find the method adapter in the bridged class
final methodAdapter = bridgedSuper.findInstanceMethodAdapter(
methodName,
);
if (methodAdapter != null) {
// Evaluate the arguments
final evaluationResult = _evaluateArgumentsAsync(node.argumentList);
if (evaluationResult is AsyncSuspensionRequest) {
return evaluationResult; // Propagate suspension
}
final (positionalArgs, namedArgs) =
evaluationResult as (List<Object?>, Map<String, Object?>);
// Evaluate type arguments for generic methods
List<RuntimeType>? evaluatedTypeArguments;
final typeArgsNode = node.typeArguments;
if (typeArgsNode != null) {
evaluatedTypeArguments = typeArgsNode.arguments
.map((typeNode) => _resolveTypeAnnotation(typeNode))
.toList();
}
// Call the adapter with the native object as target
try {
return methodAdapter(
this,
nativeSuperObject,
positionalArgs,
namedArgs,
evaluatedTypeArguments,
);
} catch (e, s) {
Logger.error(
"Native exception during super call to bridged method '${bridgedSuper.name}.$methodName': $e\n$s",
);
throw RuntimeD4rtException(
"Native error during super call to bridged method '$methodName': $e",
originalException: e,
);
}
} else {
throw RuntimeD4rtException(
"Method '$methodName' not found in bridged superclass '${bridgedSuper.name}'.",
);
}
// This block returns directly or throws an exception
}
// Handle Function.call() - all Dart functions have an implicit 'call' method
else if (targetValue is Callable && methodName == 'call') {
// Calling .call() on a function is equivalent to invoking the function
final evaluationResult = _evaluateArgumentsAsync(node.argumentList);
if (evaluationResult is AsyncSuspensionRequest) {
return evaluationResult; // Propagate suspension
}
final (positionalArgs, namedArgs) =
evaluationResult as (List<Object?>, Map<String, Object?>);
List<RuntimeType>? evaluatedTypeArguments;
final typeArgsNode = node.typeArguments;
if (typeArgsNode != null) {
evaluatedTypeArguments = typeArgsNode.arguments
.map((typeNode) => _resolveTypeAnnotation(typeNode))
.toList();
}
try {
return targetValue.call(
this,
positionalArgs,
namedArgs,
evaluatedTypeArguments,
);
} on ReturnException catch (e) {
return e.value;
}
}
//
else {
final evaluationResult = _evaluateArgumentsAsync(node.argumentList);
if (evaluationResult is AsyncSuspensionRequest) {
return evaluationResult; // Propagate suspension
}
final (positionalArgs, namedArgs) =
evaluationResult as (List<Object?>, Map<String, Object?>);
List<RuntimeType>? evaluatedTypeArguments;
final typeArgsNode = node.typeArguments;
if (typeArgsNode != null) {
evaluatedTypeArguments = typeArgsNode.arguments
.map((typeNode) => _resolveTypeAnnotation(typeNode))
.toList();
}
final extensionCallable = environment.findExtensionMember(
targetValue,
methodName,
);
if (extensionCallable is ExtensionMemberCallable) {
Logger.debug(
"[SMethodInvocation] Found extension method '$methodName'. Calling...",
);
// Prepend the target instance to the positional arguments for the extension call
final extensionPositionalArgs = [targetValue, ...positionalArgs];
try {
// Call the extension method
return extensionCallable.call(
this,
extensionPositionalArgs,
namedArgs,
evaluatedTypeArguments,
);
} on ReturnException catch (e) {
return e.value;
} catch (e) {
throw RuntimeD4rtException(
"Error executing extension method '$methodName': $e",
);
}
} else {
// RC-5b: Enum method fallback for raw native enum values
if (targetValue is Enum) {
switch (methodName) {
case 'toString':
return targetValue.toString();
case 'noSuchMethod':
break;
}
}
// GEN-C3b: Universal `Object.toString()` fallback.
// Every Dart Object has `toString()`. If a script catches a native
// exception (e.g. `RuntimeD4rtException`, `ArgumentError`,
// `JsonUnsupportedObjectError`) and calls `.toString()` on it,
// dispatch to the native method directly when no bridge or
// extension provides one. The method is no-arg and never fails.
if (methodName == 'toString' &&
positionalArgs.isEmpty &&
namedArgs.isEmpty) {
return targetValue.toString();
}
// No extension method found either, rethrow the original stdlib error
Logger.debug(
"[SMethodInvocation] Extension method '$methodName' not found. Rethrowing original error.",
);
throw RuntimeD4rtException(
"Undefined property or method '$methodName' on ${targetValue.runtimeType}.",
);
}
}
}
// Check if the resolved value is callable
if (calleeValue is Callable) {
final evaluationResult = _evaluateArgumentsAsync(node.argumentList);
if (evaluationResult is AsyncSuspensionRequest) {
return evaluationResult; // Propagate suspension
}
final (positionalArgs, namedArgs) =
evaluationResult as (List<Object?>, Map<String, Object?>);
// Evaluate Type Arguments for Method Invocation
List<RuntimeType>? evaluatedTypeArguments;
final typeArgsNode = node.typeArguments;
if (typeArgsNode != null) {
evaluatedTypeArguments = typeArgsNode.arguments
.map((typeNode) => _resolveTypeAnnotation(typeNode))
.toList();
Logger.debug(
"[SMethodInvocation] Evaluated type arguments: $evaluatedTypeArguments",
);
}
// Perform the call
try {
// The call logic now works for functions, bound instance methods,
// static methods, and constructors (which are handled by InterpretedClass.call)
// Pass the evaluated type arguments
return calleeValue.call(
this,
positionalArgs,
namedArgs,
evaluatedTypeArguments,
);
} on ReturnException catch (e) {
return e.value;
}
// Catch other potential runtime errors from the call itself
} else if (calleeValue is BridgedClass &&
(node.target == null || targetValue is Environment)) {
// GEN-101: Call of a bridged default constructor (ex: StringBuffer())
// Also handles prefixed imports (ex: ui.PictureRecorder()) where targetValue
// is the Environment. Previously the `node.target == null` check excluded
// prefixed imports, causing the BridgedClass to be returned instead of an instance.
final bridgedClass = calleeValue;
// RC-2: Evaluate type arguments (e.g., GlobalKey<NavigatorState>())
List<RuntimeType>? evaluatedTypeArguments;
final typeArgsNode = node.typeArguments;
if (typeArgsNode != null) {
evaluatedTypeArguments = typeArgsNode.arguments
.map((typeNode) => _resolveTypeAnnotation(typeNode))
.toList();
}
// RC-2: Check generic constructor registry first.
// Fires for both generic calls (GlobalKey<NavigatorState>()) and
// non-generic constructor overrides (StrutStyle()).
// Returns null to fall through to regular bridge constructor.
final genericCtor = D4.findGenericConstructor(bridgedClass.name, '');
if (genericCtor != null) {
final evaluationResult = _evaluateArgumentsAsync(node.argumentList);
if (evaluationResult is AsyncSuspensionRequest) {
return evaluationResult;
}
final (positionalArgs, namedArgs) =
evaluationResult as (List<Object?>, Map<String, Object?>);
try {
final nativeObject = D4.withActiveVisitor(
this,
() => genericCtor(
this,
positionalArgs,
namedArgs,
evaluatedTypeArguments,
),
);
// null means "fall through to regular constructor"
if (nativeObject != null) {
if (nativeObject is Future || nativeObject is Stream) {
return nativeObject;
}
if (D4.usageLogEnabled) {
D4.recordUsageHit(
'ctor',
bridgedClass.name,
evaluatedTypeArguments?.map((t) => t.toString()).join(',') ?? '',
);
}
final bridgedInstance = BridgedInstance(bridgedClass, nativeObject);
Logger.debug(
"[visitMethodInvocation] Created via generic constructor factory: ${nativeObject.runtimeType}",
);
return bridgedInstance;
}
} on RuntimeD4rtException {
rethrow;
} catch (e) {
throw RuntimeD4rtException(
"Error in generic constructor factory for '${bridgedClass.name}': $e",
);
}
}
final constructorAdapter = bridgedClass.findConstructorAdapter(
'',
); // Search for the default constructor ''
if (constructorAdapter != null) {
Logger.debug(
"[visitMethodInvocation] Calling default bridged constructor for '${bridgedClass.name}'",
);
final evaluationResult = _evaluateArgumentsAsync(node.argumentList);
if (evaluationResult is AsyncSuspensionRequest) {
return evaluationResult; // Propagate suspension
}
final (positionalArgs, namedArgs) =
evaluationResult as (List<Object?>, Map<String, Object?>);
try {
// Wrap with withActiveVisitor so that D4 helper methods can
// access the visitor for interface proxy creation (RC-1).
final nativeObject = D4.withActiveVisitor(
this,
() => constructorAdapter(this, positionalArgs, namedArgs),
);
if (nativeObject == null) {
throw RuntimeD4rtException(
"Default bridged constructor adapter for '${bridgedClass.name}' returned null.",
);
}
// Don't wrap Futures or Streams - they need to be usable directly
if (nativeObject is Future || nativeObject is Stream) {
Logger.debug(
"[visitMethodInvocation] Returning native ${nativeObject.runtimeType} directly (not wrapping)",
);
return nativeObject;
}
final bridgedInstance = BridgedInstance(bridgedClass, nativeObject);
Logger.debug(
"[visitMethodInvocation] Created BridgedInstance wrapping native: ${nativeObject.runtimeType}",
);
return bridgedInstance;
} on RuntimeD4rtException catch (e) {
throw RuntimeD4rtException(
"Error during default bridged constructor for '${bridgedClass.name}': ${e.message}",
);
} catch (e, s) {
Logger.error(
"[visitMethodInvocation] Native exception during default bridged constructor '${bridgedClass.name}': $e\n$s",
);
throw RuntimeD4rtException(
"Native error during default bridged constructor for '${bridgedClass.name}': $e",
originalException: e,
);
}
} else {
// If we have a BridgedClass but no default constructor ''
throw RuntimeD4rtException(
"'${bridgedClass.name}' is not callable (no default constructor bridge found).",
);
}
} else {
// INTER-001 FIX: Check for BridgedInstance with call() method
final bridgedResult = toBridgedInstance(calleeValue);
if (bridgedResult.$2) {
final bridgedInstance = bridgedResult.$1!;
final callMethodAdapter = bridgedInstance.bridgedClass
.findInstanceMethodAdapter('call');
if (callMethodAdapter != null) {
Logger.debug(
"[MethodInvoke] Found 'call' method on BridgedInstance (${bridgedInstance.bridgedClass.name}). Invoking...",
);
final (positionalArgs, namedArgs) = _evaluateArguments(
node.argumentList,
);
List<RuntimeType>? evaluatedTypeArguments;
final typeArgsNode = node.typeArguments;
if (typeArgsNode != null) {
evaluatedTypeArguments = typeArgsNode.arguments
.map((typeNode) => _resolveTypeAnnotation(typeNode))
.toList();
}
try {
return callMethodAdapter(
this,
bridgedInstance.nativeObject,
positionalArgs,
namedArgs,
evaluatedTypeArguments,
);
} on ReturnException catch (e) {
return e.value;
}
}
}
// Callee is NOT a standard Callable or a BridgedClass constructor
// Try Extension 'call' Method
const methodName = 'call';
try {
final extensionMethod = environment.findExtensionMember(
calleeValue,
methodName,
);
if (extensionMethod is ExtensionMemberCallable &&
!extensionMethod
.isOperator && // Ensure it's a regular method named 'call'
!extensionMethod.isGetter &&
!extensionMethod.isSetter) {
Logger.debug(
"[MethodInvoke] Found extension method 'call' for non-callable type ${calleeValue?.runtimeType}. Calling...",
);
// Need to re-evaluate args here as they weren't necessarily evaluated
// if calleeValue wasn't Callable earlier.
final (positionalArgs, namedArgs) = _evaluateArguments(
node.argumentList,
);
List<RuntimeType>? evaluatedTypeArguments;
final typeArgsNode = node.typeArguments;
if (typeArgsNode != null) {
evaluatedTypeArguments = typeArgsNode.arguments
.map((typeNode) => _resolveTypeAnnotation(typeNode))
.toList();
}
// Prepare arguments for extension method:
// First arg is the receiver (the object being called)
final extensionPositionalArgs = [calleeValue, ...positionalArgs];
try {
// Call the extension method
return extensionMethod.call(
this,
extensionPositionalArgs,
namedArgs,
evaluatedTypeArguments,
);
} on ReturnException catch (e) {
return e.value;
} catch (e) {
throw RuntimeD4rtException(
"Error executing extension method 'call': $e",
);
}
}
Logger.debug(
"[MethodInvoke] No suitable extension method 'call' found for non-callable type ${calleeValue?.runtimeType}.",
);
} on RuntimeD4rtException catch (findError) {
Logger.debug(
"[MethodInvoke] No extension member 'call' found for non-callable type ${calleeValue?.runtimeType}. Error: ${findError.message}",
);
// Fall through to the final standard error below.
}
// Original Error: The expression evaluated did not yield a callable function or an object with a callable 'call' extension.
String nameForError = "<unknown>";
if (node.target == null) {
nameForError = node.methodName!.name;
} else {
nameForError = node.toString(); // Approximate representation
}
// GEN-110 — Allow invoking native Dart Function values via the
// `MethodInvocation` shape (`fn(args)` where `fn` is a local
// parameter of a bridged builder, e.g. the `setState` argument
// of `StatefulBuilder.builder`). Without this branch the call
// would fall through to the silent `return calleeValue;` below,
// dropping the user callback. Mirrors `tom_d4rt` per the quest
// sync rule. Interpreted Callable args are wrapped via
// `D4.coerceCallableToFunction` so they satisfy the native
// function's typed parameters (e.g. `VoidCallback`).
if (calleeValue is Function) {
final evaluationResult = _evaluateArgumentsAsync(node.argumentList);
if (evaluationResult is AsyncSuspensionRequest) {
return evaluationResult;
}
final (positionalArgs, namedArgs) =
evaluationResult as (List<Object?>, Map<String, Object?>);
final wrappedPositional = positionalArgs
.map((a) => D4.coerceCallableToFunction(this, a))
.toList();
final wrappedNamed = namedArgs.map(
(k, v) => MapEntry(k, D4.coerceCallableToFunction(this, v)),
);
final symbolNamed = wrappedNamed.isEmpty
? const <Symbol, Object?>{}
: wrappedNamed
.map<Symbol, Object?>((k, v) => MapEntry(Symbol(k), v));
try {
return Function.apply(calleeValue, wrappedPositional, symbolNamed);
} on ReturnException catch (e) {
return e.value;
}
}
if (calleeValue != null) {
return calleeValue;
}
throw RuntimeD4rtException(
"'$nameForError' (type: ${calleeValue?.runtimeType}) is not callable and has no 'call' extension method.",
);
}
}