bind method

Object? bind(
  1. Environment env,
  2. Object? value
)

The value to bind — value unchanged, or its double widening — or throws D4rtTypeError when value cannot inhabit the declared type.

Implementation

Object? bind(Environment env, Object? value) {
  if (value == null) {
    if (_isNullable) return null;
    throw D4rtTypeError(
      "type 'Null' is not a subtype of type '$_displayName'$_suffix",
    );
  }

  // Above the hierarchy the subtype check can express: every non-null value
  // inhabits `Object`, and `Function` is satisfied by callables whose runtime
  // type the resolver models only coarsely.
  if (_declaredName == 'Object' || _declaredName == 'Function') return value;

  // Dart widens an `int` bound to a `double`. Without this the binding
  // receives an `int` where its own annotation promises a `double` — the same
  // silent wrong value this check exists to stop, one step further in. It is
  // also why a binding check cannot be the `is` predicate: `1 is double` is
  // false, but `for (final double d in [1, 2.5])` is a program real Dart
  // accepts, because the literal widens.
  if (_declaredName == 'double' && value is int) return value.toDouble();

  // SCE103: `{}` is a Map unless the CONTEXT type says Set — Dart's own rule,
  // and the interpreter has no context type to consult, so the literal always
  // evaluates to an empty Map and the disambiguation happens here, where the
  // written type finally is.
  //
  // NOT a concession to the declaration check that surfaced it. Measured
  // before that check existed: `Set<int> s = {}; s.add(1);` threw "Bridged
  // class 'Map' has no instance method named 'add'", and `f(Set<int> s)`
  // called with `{}` threw this very message through SCC29's parameter check.
  // Only `isEmpty` worked, because a Map answers it too. So this makes a
  // correct Dart program run where it previously threw at the second
  // statement.
  //
  // EMPTY ONLY. A non-empty Map bound to a `Set` is a genuine error and still
  // fails, which is what keeps this a disambiguation rather than a coercion
  // that hides mistakes.
  if (_declaredName == 'Set' && value is Map && value.isEmpty) {
    return <Object?>{};
  }

  final RuntimeType? valueType;
  try {
    valueType = env.getRuntimeType(value);
  } catch (_) {
    return value;
  }
  if (valueType == null || valueType is TypeParameter) return value;

  if (valueType.isSubtypeOf(_declaredType, value: value)) {
    return _checkTypeArguments(env, value);
  }

  // SCD119: the value may be a NATIVE PROXY standing in for an interpreted
  // instance. A script class that extends a bridged class is handed to
  // Flutter as a registered [D4InterpretedProxy] — `_InterpretedThemeExtension`
  // for `class BrandColors extends ThemeExtension<BrandColors>`, and the same
  // for widgets, painters and states. Ask such a value for its runtime type
  // and the answer is the BRIDGE's name (`ThemeExtension`), so binding it back
  // to a parameter declared as the script's own class was rejected with
  // `type 'ThemeExtension' is not a subtype of type 'BrandColors' of 'brand'`
  // — while every member access on the same value worked, because the
  // property and method paths already unwrap the proxy (the D2 sites in
  // `interpreter_visitor.dart`). The check was the only place that did not.
  //
  // THIS RUNS ONLY AFTER THE BASE CHECK HAS FAILED, so it can remove a
  // rejection but never add one: a program that binds today still binds.
  // That is deliberate. The obvious alternative — teaching
  // `Environment.getRuntimeType` to see through every proxy — is the more
  // correct model and a far larger blast radius, since it would change what
  // `is`, `as` and `runtimeType` answer for every proxied widget in a running
  // tree. Cluster 25 was reverted in April for exactly that kind of reach on
  // the dispatch path; this is the version that cannot regress a passing
  // script.
  //
  // The PROXY is what gets bound, not the instance behind it. The value has
  // to stay whatever native code downstream expects; only the verdict on it
  // changes.
  final Object? interpreted = _interpretedBehind(value);
  if (interpreted != null) {
    RuntimeType? interpretedType;
    try {
      interpretedType = env.getRuntimeType(interpreted);
    } catch (_) {
      interpretedType = null;
    }
    if (interpretedType != null &&
        interpretedType.isSubtypeOf(_declaredType, value: interpreted)) {
      return value;
    }
  }

  throw D4rtTypeError(
    "type '${valueType.name}' is not a subtype of type '$_displayName'"
    "$_suffix",
  );
}