visitInstanceCreationExpression method
Visit a SInstanceCreationExpression.
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.",
);
}
}