resolveBinding static method
- Environment env,
- STypeAnnotation? typeNode, {
- String? describedAs,
The value-independent half of a binding check, resolved once.
Returns null when the annotation admits everything, so the caller can skip the check entirely rather than call a predicate that always says yes.
Permissive wherever it cannot be sure. A false positive rejects a
correct program, which is worse than the silent pass these checks replace,
so every case the resolver cannot answer confidently returns null here:
unannotated bindings, dynamic / void, annotations that fail to
resolve, type parameters (which resolve to a placeholder rather than to
the type actually supplied), and the structural function/record
annotations whose comparison is a larger question than this check.
Split out from checkBindingType by SCD63 so a for-each loop can resolve its annotation once and check every element against the result. That is not a micro-optimisation: on a 200 000-element typed loop the resolution measured ~86% of the check's total cost (+16% over an unchecked loop, of which the subtype test itself was ~2%), because resolving a name walks the environment chain while comparing two resolved types does not.
Implementation
static ResolvedBinding? resolveBinding(
Environment env,
STypeAnnotation? typeNode, {
String? describedAs,
}) {
// An unannotated binding admits anything.
if (typeNode == null) return null;
// Only nominal annotations are checked. `int Function(String)` and
// `(int, String)` need a structural comparison against a callable or a
// record, and getting that wrong rejects working callbacks.
if (typeNode is! SNamedType) return null;
// Cheap spelling-level exit, before the environment lookup below. The same
// test is repeated on the RESOLVED type, which is the one that matters: a
// type parameter left unbound by a raw generic (`Box()` rather than
// `Box<int>()`) is spelled `T` but resolves to `dynamic`.
if (_isUncheckableTypeName(typeNode.name?.name ?? '')) return null;
final RuntimeType declaredType;
try {
declaredType = _resolveTypeAnnotationDynamic(typeNode, env);
} catch (_) {
// Unresolvable annotation — stay permissive, as the return-type check
// does.
return null;
}
// A type parameter stands for a type that is not bound at this point, so a
// comparison would be against the placeholder rather than against what the
// caller actually supplied.
if (declaredType is TypeParameter) return null;
final declaredName = declaredType.name;
// SCD90 removed a by-name repeat of the top-type test that used to stand
// here (`declaredName == 'dynamic' || declaredName == 'void'`). It was a
// workaround: `BridgedClass.isSubtypeOf` did not treat `dynamic` as a top
// type, so a raw generic's unbound `T` — which is spelled `T` and RESOLVES
// to `dynamic` — rejected a correct argument. The predicate answers that
// question itself now, for every kind of target, so repeating it here would
// be a spelling test standing in for a type question, which is the shape
// SCC28 spent its whole budget removing. The cheap pre-resolution exit on
// the ANNOTATION's lexeme stays: it skips an environment walk, and a
// spelling test is the only thing available before resolution.
return ResolvedBinding._(
declaredType,
declaredName,
typeNode.isNullable,
describedAs == null ? '' : " of '$describedAs'",
_appliedDeclaredType(env, typeNode, declaredType),
);
}