visitSetOrMapLiteral method
Visit a SSetOrMapLiteral.
Implementation
@override
Object? visitSetOrMapLiteral(SSetOrMapLiteral node) {
// T3 (perf, F4): a const set/map literal is canonical — return the cached
// instance instead of re-inferring the type, re-evaluating elements and
// re-allocating an unmodifiable view on every visit.
if (node.isConst) {
final cached = _constCollectionCache[node];
if (cached != null) return cached;
}
bool isMap;
bool typeExplicit = false;
// Check explicit type arguments first
if (node.typeArguments != null &&
node.typeArguments!.arguments.isNotEmpty) {
isMap = node.typeArguments!.arguments.length == 2;
typeExplicit = true;
Logger.debug(
"[SSetOrMapLiteral] Determined type via explicit args: isMap = $isMap",
);
} else {
// No explicit types, infer from content
isMap = false; // Default to Set if unsure initially
bool onlySpreads = true;
SAstNode? firstEffectiveElement;
for (final element in node.elements) {
// Check if element (or its inner body) is a SMapLiteralEntry
if (_isMapLiteralElement(element)) {
isMap = true;
onlySpreads = false;
firstEffectiveElement = element;
Logger.debug(
"[SSetOrMapLiteral] Determined isMap = true (found SMapLiteralEntry or element containing one).",
);
break; // Found a map entry, definitely a map
}
if (element is! SSpreadElement && firstEffectiveElement == null) {
firstEffectiveElement = element;
onlySpreads = false;
// If it's an Expression, isMap remains false (it's a Set)
Logger.debug(
"[SSetOrMapLiteral] Determined isMap = false (found first non-spread Expression).",
);
}
if (element is! SSpreadElement) {
onlySpreads = false;
}
}
// Handle empty literal or spread-only literal
if (!typeExplicit) {
// Re-evaluate if type wasn't explicit
if (node.elements.isEmpty) {
isMap = true; // Empty literal defaults to Map
Logger.debug(
"[SSetOrMapLiteral] Determined isMap = true (empty literal).",
);
} else if (onlySpreads) {
// Only spread elements, no explicit type args. Infer from first spread.
Logger.debug(
"[SSetOrMapLiteral] Only spreads found. Inferring type from first spread element.",
);
final firstSpread = node.elements.first as SSpreadElement;
final spreadValue = firstSpread.expression!.accept<Object?>(this);
// Check if the evaluated spread value is a Map or looks like one
final bridgedInstance = toBridgedInstance(spreadValue);
if (spreadValue is Map ||
(bridgedInstance.$2 && bridgedInstance.$1?.nativeObject is Map)) {
isMap = true;
Logger.debug(
"[SSetOrMapLiteral] First spread evaluated to Map. Setting isMap = true.",
);
} else {
isMap = false; // Assume Set otherwise
Logger.debug(
"[SSetOrMapLiteral] First spread did not evaluate to Map. Setting isMap = false.",
);
}
} else if (firstEffectiveElement != null &&
_isMapLiteralElement(firstEffectiveElement)) {
isMap =
true; // Confirmation if first non-spread was entry (or contains one)
} else {
isMap = false; // If first non-spread was Expression, it's a Set
}
}
}
// Create and populate the collection
final Object collection = isMap ? <Object?, Object?>{} : <Object?>{};
for (final element in node.elements) {
try {
_processCollectionElement(element, collection, isMap: isMap);
} on RuntimeD4rtException catch (e) {
final literalType = isMap ? "Map" : "Set";
// Check if error already contains context to avoid duplication
if (!e.message.contains('in $literalType literal')) {
throw RuntimeD4rtException("${e.message} (in $literalType literal)");
} else {
rethrow; // Rethrow original error with context
}
}
}
// If this is a const collection, return an unmodifiable version and cache
// it (T3): the same literal node must canonicalize to one instance.
if (node.isConst) {
final Object canonical = isMap
? Map.unmodifiable(collection as Map<Object?, Object?>)
: Set.unmodifiable(collection as Set<Object?>);
_constCollectionCache[node] = canonical;
return canonical;
}
return collection;
}