bind method
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",
);
}