visitReturnStatement method

  1. @override
Object? visitReturnStatement(
  1. SReturnStatement node
)
override

Visit a SReturnStatement.

Implementation

@override
Object? visitReturnStatement(SReturnStatement node) {
  Object? returnValue;
  if (node.expression != null) {
    returnValue = node.expression!.accept<Object?>(this);
    if (returnValue is AsyncSuspensionRequest) {
      return returnValue;
    }
  } else {
    returnValue = null;
  }

  // Use currentFunction (set by InterpretedFunction.call) to get the
  // declared return type. SAstNode has no parent references, so we cannot
  // walk up the AST to find the enclosing function declaration.
  final currentCallable = currentFunction;
  if (currentCallable != null) {
    bool isNullable = currentCallable.isNullable;
    // Extract function name from the callable's toString, or use a default.
    // InterpretedFunction.toString returns '<fn name>'.
    final fnStr = currentCallable.toString();
    String functionName = fnStr;
    if (fnStr.startsWith('<fn ') && fnStr.endsWith('>')) {
      functionName = fnStr.substring(4, fnStr.length - 1);
    }
    RuntimeType? declaredType = currentCallable.declaredReturnType;
    RuntimeType? valueRuntimeType;

    try {
      // Special handling for async* generators: return without value should be allowed
      if (currentCallable.isAsyncGenerator && returnValue == null) {
        throw ReturnException(returnValue); // Exit generator cleanly
      }

      valueRuntimeType = environment.getRuntimeType(returnValue);

      // Guard: skip building these interpolated diagnostics on every return
      // when debug logging is off.
      if (Logger.isDebug) {
        Logger.debug("[visitReturnStatement] Function: '$functionName'");
        Logger.debug(
          "[visitReturnStatement]   Declared Type: ${declaredType?.name ?? 'N/A'}",
        );
        Logger.debug(
          "[visitReturnStatement]   Value Runtime Type: ${valueRuntimeType?.name ?? 'N/A'}",
        );
        Logger.debug(
          "[visitReturnStatement]   Return Value: $returnValue (Type: ${returnValue?.runtimeType})",
        );
        Logger.debug(
          "[visitReturnStatement]   Is Declared Type Nullable: $isNullable",
        );
      }

      // Check null return value against non-nullable declared type
      if (returnValue == null &&
          declaredType != null &&
          !isNullable &&
          declaredType.name != 'void' &&
          declaredType.name != 'dynamic') {
        final declaredTypeName = declaredType.name;
        throw RuntimeD4rtException(
          "A value of type 'Null' can't be returned from the function '$functionName' because it has a return type of '$declaredTypeName'.",
        );
      }

      if (valueRuntimeType != null) {
        if (declaredType != null) {
          if (declaredType.name != "dynamic" &&
              !valueRuntimeType.isSubtypeOf(
                declaredType,
                value: returnValue,
              )) {
            bool showError = true;
            if (isNullable && returnValue == null) {
              showError = false;
            }
            if (declaredType.name == "void" && returnValue == null) {
              showError = false;
            }
            if (declaredType.name == "Object" && returnValue != null) {
              showError = false;
            }
            // Bug-73 FIX: In async functions, returning a Future<T> when declared type is T is allowed
            if (currentCallable.isAsync && returnValue is Future) {
              showError = false;
            }

            // Bug-93 FIX: Dart implicitly promotes int to double
            if (declaredType.name == 'double' && returnValue is int) {
              showError = false;
              returnValue = returnValue.toDouble();
            }

            if (showError) {
              final declaredTypeName = isNullable
                  ? '${declaredType.name}?'
                  : declaredType.name;
              throw RuntimeD4rtException(
                "A value of type '${valueRuntimeType.name}' can't be returned from the function '$functionName' because it has a return type of '$declaredTypeName'.",
              );
            }
          }
        }
      }
    } catch (e) {
      Logger.error(
        "[visitReturnStatement] Error during type check for function '$functionName': $e",
      );
      if (e is Error) {
        Logger.error("Stack trace: ${e.stackTrace}");
      }
      rethrow;
    }
  }

  // For non-suspended results, throw the exception to unwind the stack.
  throw ReturnException(returnValue);
}