visitInstanceCreationExpression method

  1. @override
Object? visitInstanceCreationExpression(
  1. SInstanceCreationExpression node
)
override

Implementation

@override
Object? visitInstanceCreationExpression(SInstanceCreationExpression node) {
  final constructorNameNode = node.constructorName!.type;

  // Resolve class name and named constructor, handling unresolved AST
  // ambiguity where the parser can't distinguish ClassName.namedCtor(...)
  // from importPrefix.ClassName(...). In unresolved AST, ClassName goes
  // into SNamedType.importPrefix and namedCtor goes into SNamedType.name,
  // with SConstructorName.name being null.
  String constructorName;
  String? namedConstructorPart;
  String? resolvedImportPrefix; // Track real import prefix for resolution

  if (node.constructorName!.name != null) {
    // Resolved form: type.name = class, constructorName.name = named ctor
    // May also have importPrefix for prefix.ClassName.namedCtor() calls
    constructorName = constructorNameNode!.name!.name;
    namedConstructorPart = node.constructorName!.name!.name;
    if (constructorNameNode.importPrefix != null) {
      resolvedImportPrefix = constructorNameNode.importPrefix!.name;
    }
  } else if (constructorNameNode!.importPrefix != null) {
    // Unresolved ambiguity: check if importPrefix is actually a class name
    final possibleClassName = constructorNameNode.importPrefix!.name;
    Object? possibleType;
    try {
      possibleType = environment.get(possibleClassName);
    } on RuntimeD4rtException {
      possibleType = null;
    }
    if (possibleType is InterpretedClass || possibleType is BridgedClass) {
      // importPrefix was actually the class name, name is the named ctor
      constructorName = possibleClassName;
      namedConstructorPart = constructorNameNode.name!.name;
    } else {
      // It really is a prefix.ClassName() call
      constructorName = constructorNameNode.name!.name;
      resolvedImportPrefix = constructorNameNode.importPrefix!.name;
      namedConstructorPart = null;
    }
  } else {
    // Simple: ClassName() with no prefix, no named constructor
    constructorName = constructorNameNode.name!.name;
    namedConstructorPart = null;
  }

  Logger.debug(
    "[InstanceCreation] Creating instance of '$constructorName'${namedConstructorPart != null ? '.$namedConstructorPart' : ''}",
  );

  // Resolve the type — use prefix if present so that e.g. ui.StrutStyle
  // resolves through the dart:ui prefixed environment, not the current scope.
  final lookupName = resolvedImportPrefix != null
      ? '$resolvedImportPrefix.$constructorName'
      : constructorName;
  Object? typeValue;
  try {
    typeValue = environment.get(lookupName);
  } on RuntimeD4rtException {
    throw RuntimeD4rtException(
      "Type '$constructorName' not found for instantiation.",
    );
  }

  // Check the resolved type
  if (typeValue is InterpretedClass) {
    // CASE 1: InterpretedClass
    final klass = typeValue;
    Logger.debug(
      "[InstanceCreation]   Type resolved to InterpretedClass: '$constructorName'",
    );

    // RC-3: If the interpreted class extends a bridged type that has a
    // registered interface proxy (e.g. StatefulWidget, StatelessWidget),
    // convert the InterpretedInstance to its native proxy so that runtime
    // type checks like `is Widget` pass naturally.
    Object applyInterfaceProxy(InterpretedInstance instance) {
      return D4.tryCreateInterfaceProxyWithVisitor<Object>(instance, this) ??
          instance;
    }

    // Find and call the constructor (interpreted)
    final constructorLookupName =
        namedConstructorPart ?? ''; // Use '' for default
    final constructor = klass.findConstructor(constructorLookupName);

    if (constructor == null) {
      throw RuntimeD4rtException(
        "Class '$constructorName' does not have a constructor named '$constructorLookupName'.",
      );
    }

    // G-DOV2-3 FIX: Check if the class is abstract AFTER finding the constructor
    // Factory constructors are allowed on abstract classes
    if (klass.isAbstract && !constructor.isFactory) {
      throw RuntimeD4rtException(
        "Cannot instantiate abstract class '$constructorName'.",
      );
    }

    // 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?>);

    try {
      // Evaluate the type arguments
      List<RuntimeType>? evaluatedTypeArguments;
      final typeArgsNode = node.constructorName!.type!.typeArguments;
      if (typeArgsNode != null) {
        evaluatedTypeArguments = typeArgsNode.arguments
            .map((typeNode) => _resolveTypeAnnotation(typeNode))
            .toList();
      }

      // Handle factory constructors differently from regular constructors
      if (constructor.isFactory) {
        // Factory constructors don't need a pre-created instance
        // They are responsible for creating and returning their own instance
        Logger.debug(
          "[InstanceCreation] Calling factory constructor '$constructorLookupName'",
        );

        // Call the factory constructor directly without creating an instance first
        // The factory will create its own instance and return it
        final result = constructor.call(
          this,
          positionalArgs,
          namedArgs,
          evaluatedTypeArguments,
        );

        // Factory constructors should return an instance of the expected type
        if (result is InterpretedInstance && result.klass == klass) {
          return applyInterfaceProxy(result);
        } else if (result is InterpretedInstance) {
          throw RuntimeD4rtException(
            "Factory constructor '$constructorLookupName' returned an instance of '${result.klass.name}' but expected '$constructorName'.",
          );
        } else {
          throw RuntimeD4rtException(
            "Factory constructor '$constructorLookupName' must return an instance, but returned ${result?.runtimeType}.",
          );
        }
      } else {
        // Regular constructors: create instance first, then call constructor
        Logger.debug(
          "[InstanceCreation] Calling regular constructor '$constructorLookupName'",
        );

        // Create and initialize the fields, passing the type arguments
        final instance = klass.createAndInitializeInstance(
          this,
          evaluatedTypeArguments,
        );
        // Bind 'this' and call the constructor logic
        final boundConstructor = constructor.bind(instance);
        boundConstructor.call(
          this,
          positionalArgs,
          namedArgs,
          evaluatedTypeArguments,
        );
        // The constructor call returns the instance
        return applyInterfaceProxy(instance);
      }
    } on ReturnException catch (e) {
      // Handle return exceptions (applies to both factory and regular constructors)
      if (constructor.isFactory) {
        // For factory constructors, the return value is the actual result
        final factoryResult = e.value;
        if (factoryResult is InterpretedInstance) {
          return applyInterfaceProxy(factoryResult);
        }
        return factoryResult;
      } else {
        // For regular constructors, check if returned value is valid
        if (e.value != null && e.value is InterpretedInstance) {
          final instance = e.value as InterpretedInstance; // Explicit cast
          if (instance.klass == klass) {
            // Check on the casted instance
            return applyInterfaceProxy(instance);
          }
        }
        // If the condition fails (null, not InterpretedInstance, or wrong class)
        throw RuntimeD4rtException(
          "Constructor return value error for '$constructorName'.",
        );
      }
    } on RuntimeD4rtException catch (e) {
      // Simplified error message
      throw RuntimeD4rtException(
        "Constructor execution error for '$constructorName.': ${e.message}",
      );
    }
  } else if (typeValue is BridgedClass) {
    // CASE 2: BridgedClass
    final bridgedClass = typeValue;
    Logger.debug(
      "[InstanceCreation]   Type resolved to BridgedClass: '$constructorName'",
    );

    final (positionalArgs, namedArgs) = _evaluateArguments(node.argumentList);

    // RC-2: Evaluate type arguments for BridgedClass constructors
    List<RuntimeType>? evaluatedTypeArguments;
    final typeArgsNode = node.constructorName!.type!.typeArguments;
    if (typeArgsNode != null) {
      evaluatedTypeArguments = typeArgsNode.arguments
          .map((typeNode) => _resolveTypeAnnotation(typeNode))
          .toList();
    }

    // Find the constructor adapter (bridged)
    final constructorLookupName =
        namedConstructorPart ?? ''; // Use '' if null

    // RC-2: Check for a registered generic constructor factory 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(
        constructorName,
        constructorLookupName,
      );
      if (genericCtor != null) {
        try {
          final nativeObject = 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',
                constructorName,
                evaluatedTypeArguments?.map((t) => t.toString()).join(',') ??
                    '',
              );
            }
            final bridgedInstance = BridgedInstance(
              bridgedClass,
              nativeObject,
            );
            Logger.debug(
              "[InstanceCreation]   Created via generic constructor factory: ${nativeObject.runtimeType}",
            );
            return bridgedInstance;
          }
        } on RuntimeD4rtException {
          rethrow;
        } catch (e) {
          throw RuntimeD4rtException(
            "Error in generic constructor factory for '$constructorName': $e",
          );
        }
      }
    }

    final constructorAdapter = bridgedClass.findConstructorAdapter(
      constructorLookupName,
    );

    if (constructorAdapter == null) {
      // GEN-129 (generic factory-as-static fix): some stdlib/Flutter
      // bridges register factory constructors as `staticMethods` (e.g.
      // Stream.periodic, Stream.value, Future.value). When the source
      // adds type arguments — `Stream<int>.periodic(...)` — the
      // analyzer parses the call as an InstanceCreationExpression and
      // routes us here; without type args it would be a
      // MethodInvocation routed through static-method lookup. To make
      // both forms behave identically we fall back to staticMethods
      // before giving up. This is fully generic — no Stream- or
      // Flutter-specific special-casing.
      if (constructorLookupName.isNotEmpty) {
        final staticAdapter = bridgedClass.findStaticMethodAdapter(
          constructorLookupName,
        );
        if (staticAdapter != null) {
          try {
            final nativeObject = D4.withActiveVisitor(
              this,
              () => staticAdapter(
                this,
                positionalArgs,
                namedArgs,
                evaluatedTypeArguments,
              ),
            );
            if (nativeObject == null) {
              throw RuntimeD4rtException(
                "Bridged static-as-constructor adapter for '$constructorName.$constructorLookupName' returned null unexpectedly.",
              );
            }
            if (nativeObject is Future || nativeObject is Stream) {
              return nativeObject;
            }
            return BridgedInstance(
              bridgedClass,
              nativeObject,
              typeArguments: evaluatedTypeArguments ?? const [],
            );
          } on RuntimeD4rtException {
            rethrow;
          } catch (e) {
            throw RuntimeD4rtException(
              "Error during bridged static-as-constructor '$constructorLookupName' for class '$constructorName': $e",
            );
          }
        }
      }
      throw RuntimeD4rtException(
        "Bridged class '$constructorName' does not have a registered constructor named '$constructorLookupName'. Check bridge definition.",
      );
    }

    // Call the constructor adapter
    try {
      // The adapter is responsible for:
      // 1. Converting the interpreted positionalArgs/namedArgs to native types.
      // 2. Calling the actual native constructor.
      // 3. Returning the created native object.
      // Wrap with withActiveVisitor so that D4 helper methods (getRequiredNamedArg
      // etc.) can access the visitor for interface proxy creation (RC-1).
      final nativeObject = D4.withActiveVisitor(
        this,
        () => constructorAdapter(this, positionalArgs, namedArgs),
      );

      // Check if the adapter returned a value (it should)
      if (nativeObject == null) {
        throw RuntimeD4rtException(
          "Bridged constructor adapter for '\$constructorName.$constructorLookupName' returned null unexpectedly.",
        );
      }

      // Don't wrap Futures in BridgedInstance - they need to be awaitable directly
      // Similarly, don't wrap Streams as they need direct subscription
      if (nativeObject is Future || nativeObject is Stream) {
        Logger.debug(
          "[InstanceCreation]   Returning native ${nativeObject.runtimeType} directly (not wrapping)",
        );
        return nativeObject;
      }

      // Wrap the native object in BridgedInstance
      final bridgedInstance = BridgedInstance(bridgedClass, nativeObject);
      Logger.debug(
        "[InstanceCreation]   Successfully created BridgedInstance wrapping native object: \${nativeObject.runtimeType}",
      );
      return bridgedInstance;
    } on RuntimeD4rtException catch (e) {
      // If the adapter itself raises a RuntimeError (e.g. conversion failure)
      throw RuntimeD4rtException(
        "Error during bridged constructor '$constructorLookupName' for class '$constructorName': ${e.message}",
      );
    } catch (e) {
      // Catch potential native exceptions raised by the adapter or the native constructor
      Logger.error(
        "[InstanceCreation] Native exception during bridged constructor '$constructorName.$constructorLookupName': \$e\\n\$s",
      );
      // Encapsulate the native error in a RuntimeError for propagation
      throw RuntimeD4rtException(
        "Native error during bridged constructor '$constructorLookupName' for class '$constructorName': $e",
        originalException: e,
      );
    }
  } else {
    // CASE 3: The resolved type is neither InterpretedClass nor BridgedClass
    throw RuntimeD4rtException(
      "Identifier '$constructorName' resolved to ${typeValue?.runtimeType}, which is not a class type that can be instantiated.",
    );
  }
}