toBridgedInstance method
Converts a native object to a bridged instance if a bridge exists.
nativeObject The native object to convert.
Returns a BridgedInstance if a bridge is found for the object's type, otherwise throws a D4rtException (caller should handle).
Resolution order:
- Direct lookup by Type (most specific — bridges with explicit BridgedClass.nativeType).
- BridgedClass.isAssignable iteration — keeps the LAST match across
all enclosing environments. Bridges register general→specific, so
the last match is the most specific (e.g., CupertinoTextThemeData
wins over Diagnosticable). Required to disambiguate native types
whose name happens to be a prefix of another bridge's name —
e.g.,
StringCharacters implements Characters: without this step the name-prefix fallback in toBridgedClass would wrap it asString. Bucket #11 fix. - toBridgedClass name-based fallbacks (private impl types,
generic suffix matching,
*Implprefix matching). Used only when neither direct type lookup nor isAssignable found a bridge.
Implementation
BridgedInstance? toBridgedInstance(Object? nativeObject) {
if (nativeObject == null) {
return null;
}
final runtimeType = nativeObject.runtimeType;
// 1) Direct type lookup.
Environment? current = this;
while (current != null) {
final direct = current._bridgedClassesLookupByType[runtimeType];
if (direct != null) {
return BridgedInstance(direct, nativeObject);
}
current = current._enclosing;
}
// 1b) Resolution cache. GEN-115 Phase 2: a previous call already paid
// the full step-2 / step-3 cost for this runtimeType; reuse it.
// Walks the env chain so a hit in any enclosing scope short-circuits.
current = this;
while (current != null) {
final cached = current._resolvedTypeCache[runtimeType];
if (cached != null) {
return BridgedInstance(cached, nativeObject);
}
// T4 (perf, F3): negative-cache hit — a previous call already proved this
// runtimeType has no bridge. Re-throw the same miss without re-walking the
// chain, iterating every bridge, or running the name-fallback toString.
if (current._unbridgedTypeCache.contains(runtimeType)) {
throw RuntimeD4rtException(
'Cannot bridge native object: No registered bridged class found '
'for native type $runtimeType.');
}
current = current._enclosing;
}
// 2) isAssignable iteration. Bridges may register in any order, so we
// collect ALL matches and then drop those that are supertypes of
// another match using [BridgedClass.transitiveSupertypeNames]. The
// remaining set is "leaf" matches; we pick the last one (preserves
// legacy LAST-wins behaviour when the registry doesn't disambiguate).
//
// D2 fix: A native object whose runtimeType is a private impl of
// BoxConstraints (e.g. `_BodyBoxConstraints`) was wrapped as
// `Constraints` because the LAST-match-wins iteration picked the
// abstract base. With `BoxConstraints: [Constraints, ...]` registered
// in the supertype registry, the filter drops `Constraints` and
// keeps `BoxConstraints`, so `.maxWidth` resolves correctly.
final allMatches = <BridgedClass>[];
current = this;
while (current != null) {
for (final entry in current._bridgedClassesLookupByType.entries) {
final bridge = entry.value;
if (bridge.isAssignable != null && bridge.isAssignable!(nativeObject)) {
allMatches.add(bridge);
}
}
current = current._enclosing;
}
if (allMatches.isNotEmpty) {
final filtered = _filterToMostSpecific(allMatches);
final picked = filtered.isNotEmpty ? filtered.last : allMatches.last;
_resolvedTypeCacheOrNew[runtimeType] = picked;
return BridgedInstance(picked, nativeObject);
}
// 3) Name-based fallbacks (private impl, generic suffix, *Impl prefix).
// [toBridgedClass] will throw if no bridge matches — propagate, but
// negative-cache the miss first so repeats short-circuit (T4, F3).
final BridgedClass bridgedClass;
try {
bridgedClass = toBridgedClass(runtimeType);
} on RuntimeD4rtException {
_unbridgedTypeCacheOrNew.add(runtimeType);
rethrow;
}
_resolvedTypeCacheOrNew[runtimeType] = bridgedClass;
return BridgedInstance(bridgedClass, nativeObject);
}