visitIsExpression method
Visit a SIsExpression.
Implementation
@override
Object? visitIsExpression(SIsExpression node) {
final expressionValue = node.expression!.accept<Object?>(this);
final typeNode = node.type;
bool result = false;
if (typeNode is SNamedType) {
final rawTypeName = typeNode.name!.name;
// GEN-100c: Handle import-prefixed type checks (e.g., value is ui.Color)
final typeName = typeNode.importPrefix != null
? '${typeNode.importPrefix!.name}.$rawTypeName'
: rawTypeName;
// Handle built-in types first
switch (typeName) {
case 'int':
result = expressionValue is int;
break;
case 'double':
result = expressionValue is double;
break;
case 'num':
result = expressionValue is num;
break;
case 'String':
result = expressionValue is String;
break;
case 'bool':
result = expressionValue is bool;
break;
case 'List':
if (expressionValue is! List) {
result = false;
} else if (typeNode.typeArguments == null ||
typeNode.typeArguments!.arguments.isEmpty) {
// No type arguments specified, just check if it's a List
result = true;
} else {
// Check generic type arguments
result = _checkGenericListType(
expressionValue,
typeNode.typeArguments!.arguments[0],
);
}
break;
case 'Map':
if (expressionValue is! Map) {
result = false;
} else if (typeNode.typeArguments == null ||
typeNode.typeArguments!.arguments.isEmpty) {
// No type arguments specified, just check if it's a Map
result = true;
} else {
// Check generic type arguments
final typeArgs = typeNode.typeArguments!.arguments;
if (typeArgs.length >= 2) {
result = _checkGenericMapType(
expressionValue,
typeArgs[0],
typeArgs[1],
);
} else {
result = true; // Partial generic, just accept
}
}
break;
case 'Null':
result = expressionValue == null;
break;
case 'Object':
// Everything non-null is an Object?
result = expressionValue != null;
break;
case 'dynamic': // 'is dynamic' is always true
case 'void': // 'is void' is always false (or error?) - let's say false
result = typeName == 'dynamic';
break;
default:
try {
final targetType = environment.get(typeName);
if (targetType is BridgedClass) {
// Resolve `is BridgedX` for any operand shape:
// • BridgedInstance — try the registered supertype walk,
// fall through to the native `is`
// predicate as a last resort.
// • Raw native object — common when a Flutter callback
// passes its native argument
// unwrapped (e.g. KeyEvent on
// KeyboardListener.onKeyEvent).
// Defer directly to the bridge's
// `isAssignable` predicate.
// • null / interpreted — `is BridgedX` is false.
result = false;
Object? nativeValue;
if (expressionValue is BridgedInstance) {
if (expressionValue.bridgedClass.isSubtypeOf(targetType)) {
result = true;
} else {
nativeValue = expressionValue.nativeObject;
}
} else if (expressionValue != null &&
expressionValue is! InterpretedInstance) {
nativeValue = expressionValue;
}
// `isAssignable` closes over the host's native `v is X`
// operator — authoritative when the bridge-name supertype
// walk misses (incomplete registry) or the operand was
// never wrapped in the first place.
if (!result &&
nativeValue != null &&
targetType.isAssignable != null) {
result = targetType.isAssignable!(nativeValue);
}
} else if (targetType is InterpretedClass) {
if (expressionValue is InterpretedInstance) {
// Use the new helper method
result = expressionValue.klass.isSubtypeOf(targetType);
} else {
// A non-instance value cannot be a subtype of a user-defined class
result = false;
}
} else if (targetType is NativeFunction &&
targetType.call(this, []) is Type) {
final object = targetType.call(this, []);
return expressionValue.runtimeType == object;
} else {
throw RuntimeD4rtException(
"Type '$typeName' not found or is not a ${expressionValue.runtimeType}.",
);
}
} on RuntimeD4rtException catch (e) {
// Propagate type lookup error
// Wrap in InternalInterpreterException to be caught correctly
throw InternalInterpreterD4rtException(
RuntimeD4rtException("Type check failed: ${e.message}"),
);
}
}
} else {
// Handle FunctionType, etc., later if needed
throw UnimplementedD4rtException(
'Type check for ${typeNode.runtimeType} not implemented.',
);
}
// Handle negation (is!)
if (node.isNot) {
return !result;
} else {
return result;
}
}