visitMethodInvocation method

  1. @override
Object? visitMethodInvocation(
  1. SMethodInvocation node
)
override

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.",
    );
  }
}