call method

  1. @override
Object? call(
  1. InterpreterVisitor visitor,
  2. List<Object?> positionalArguments, [
  3. Map<String, Object?> namedArguments = const {},
  4. List<RuntimeType>? typeArguments,
])
override

Implementation

@override
Object? call(InterpreterVisitor visitor, List<Object?> positionalArguments,
    [Map<String, Object?> namedArguments = const {},
    List<RuntimeType>? typeArguments]) {
  // 1. Extract the target instance (first argument)
  if (positionalArguments.isEmpty) {
    throw RuntimeD4rtException(
        "Internal error: Extension method '${declaration.name!.name}' called without target instance ('this').");
  }
  final targetInstance =
      positionalArguments.removeAt(0); // Consomme le premier argument

  // 2. Create the execution environment
  final executionEnvironment = Environment(enclosing: closure);
  // Define 'this' in this environment
  executionEnvironment.define('this', targetInstance);
  Logger.debug(
      "[InterpretedExtensionMethod.call] Created execution env (${executionEnvironment.hashCode}) for '${declaration.name!.name}', defining 'this'=${targetInstance?.runtimeType}");

  // 3. Bind the declared parameters (explicit arguments)
  final params = declaration.parameters?.parameters;
  int positionalArgIndex = 0;
  final providedNamedArgs = namedArguments;
  final processedParamNames = <String>{};

  if (params != null) {
    for (final param in params) {
      String? paramName;
      SAstNode? defaultValueExpr;
      bool isRequired = false;
      bool isOptionalPositional = false;
      bool isNamed = false;
      bool isRequiredNamed = false;

      // Determine the parameter info (copied from InterpretedFunction)
      SAstNode actualParam = param;
      if (param is SDefaultFormalParameter) {
        defaultValueExpr = param.defaultValue;
        actualParam = param.parameter ?? param;
      }
      if (actualParam is SSimpleFormalParameter) {
        paramName = actualParam.name?.name;
        isRequired = actualParam.isPositional && actualParam.isRequired;
        isOptionalPositional =
            actualParam.isPositional && !actualParam.isRequired;
        isNamed = actualParam.isNamed;
        isRequiredNamed = actualParam.isNamed && actualParam.isRequired;
      } else {
        throw UnimplementedD4rtException(
            "Unsupported parameter kind in extension method: ${actualParam.runtimeType}");
      }
      if (paramName == null) {
        throw StateD4rtException("Extension parameter missing name");
      }
      processedParamNames.add(paramName);

      // Find the corresponding argument and value
      Object? valueToDefine;
      bool argumentProvided = false;
      if (isOptionalPositional || isRequired) {
        if (positionalArgIndex < positionalArguments.length) {
          valueToDefine = positionalArguments[positionalArgIndex++];
          argumentProvided = true;
        }
      } else if (isNamed) {
        if (providedNamedArgs.containsKey(paramName)) {
          valueToDefine = providedNamedArgs[paramName];
          argumentProvided = true;
        }
      }

      // Handle default values and required checks
      if (!argumentProvided) {
        if (defaultValueExpr != null) {
          final previousVisitorEnv = visitor.environment;
          try {
            visitor.environment =
                closure; // Defaults evaluated in declaration scope
            valueToDefine = defaultValueExpr.accept<Object?>(visitor);
          } finally {
            visitor.environment = previousVisitorEnv;
          }
        } else if (isRequired || isRequiredNamed) {
          throw RuntimeD4rtException(
              "Missing required ${isNamed ? 'named' : ''} argument for '$paramName' in extension method '${declaration.name!.name}'.");
        } else {
          valueToDefine = null;
        }
      }
      // Define the variable in the execution environment
      executionEnvironment.define(paramName, valueToDefine);
      Logger.debug(
          " [InterpretedExtensionMethod.call] Bound param '$paramName' = $valueToDefine");
    }

    // Final argument checks (copied from InterpretedFunction)
    final int totalPositionalDeclared = params
        .where((p) =>
            (p is SSimpleFormalParameter && p.isPositional) ||
            (p is SDefaultFormalParameter && p.isPositional))
        .length;
    if (positionalArgIndex < positionalArguments.length) {
      throw RuntimeD4rtException(
          "Too many positional arguments for extension method '${declaration.name!.name}'. Expected at most $totalPositionalDeclared, got ${positionalArguments.length}.");
    }
    for (final providedName in providedNamedArgs.keys) {
      if (!processedParamNames.contains(providedName)) {
        throw RuntimeD4rtException(
            "Extension method '${declaration.name!.name}' does not have a parameter named '$providedName'.");
      }
    }
  } else if (positionalArguments.isNotEmpty || providedNamedArgs.isNotEmpty) {
    throw RuntimeD4rtException(
        "Extension method '${declaration.name!.name}' takes no arguments (besides 'this'), but arguments were provided.");
  }

  // 4. Execute the body in the new environment
  final previousEnvironment = visitor.environment;
  final previousFunction = visitor.currentFunction;
  // G-DOV2-7 FIX: install an InterpretedFunction.method wrapper so that
  // `visitReturnStatement` reads *this* extension method's declared return
  // type (and matching name) instead of falling back to the caller's
  // function (typically `main` declared `void`). Without this, returning a
  // value from an extension getter on an enum incorrectly tripped the
  // String-vs-void check on `main`.
  visitor.currentFunction =
      InterpretedFunction.method(declaration, closure, onType);
  visitor.environment = executionEnvironment; // USE THE NEW ENVIRONMENT
  Logger.debug(
      "[InterpretedExtensionMethod.call] Set visitor environment to executionEnvironment (${executionEnvironment.hashCode}) before executing body.");

  try {
    final body = declaration.body;
    if (body is SBlockFunctionBody) {
      // executeBlock already handles ReturnException correctly
      return visitor.executeBlock(
          body.block!.statements, executionEnvironment);
    } else if (body is SExpressionFunctionBody) {
      // For an expression body, evaluate and return the value
      final result = body.expression!.accept<Object?>(visitor);
      // No need to raise ReturnException here, we return directly
      return result;
    } else if (body is SEmptyFunctionBody) {
      throw RuntimeD4rtException(
          "Cannot execute empty body for extension method '${declaration.name!.name}'.");
    } else {
      throw UnimplementedD4rtException(
          'Function body type not handled in extension method: ${body.runtimeType}');
    }
  } on ReturnException catch (e) {
    // Catch in case executeBlock (or other) still raises ReturnException
    Logger.debug(
        " [InterpretedExtensionMethod.call] Caught ReturnException, returning value: ${e.value}");
    return e.value;
  } finally {
    Logger.debug(
        " [InterpretedExtensionMethod.call] Restoring visitor environment from (${visitor.environment.hashCode}) to (${previousEnvironment.hashCode}).");
    visitor.environment = previousEnvironment;
    visitor.currentFunction =
        previousFunction; // Restaurer aussi currentFunction
  }
}