visitIsExpression method

  1. @override
Object? visitIsExpression(
  1. SIsExpression node
)
override

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;
  }
}