generateWidgetReCreator function
Emits a D4.registerGenericTypeWrapper re-creator block for a single
type-parameter immutable widget className (recreatorClasses).
Some Flutter widgets (DropdownMenuItem<T>, DropdownMenuEntry<T>,
ButtonSegment<T>) cannot be wrapped by a $Relaxed subclass — they are
immutable and drive complex rendering. Instead they are RE-CONSTRUCTED with
the correct <T> by reading each constructor parameter back from its
same-named instance getter. This replaces the hand-written re-creators in
d4rt_runtime_registrations.dart.
The emitted block is 2-space-indented, ready to drop inside
registerRelaxers(). The switch has a leading dynamic/Object/Object?
arm (no value cast), one arm per innerTypes entry (the single type-param
constructor parameter cast to that type), and a _ => null fallthrough.
Returns null (and calls warn) when the class cannot be safely
re-created: not exactly one type parameter, no default constructor, a
constructor parameter without a matching instance getter, more than one
parameter referencing the type parameter, or a parameter that references
the type parameter in a non-bare form (e.g. List<T>).
Implementation
String? generateWidgetReCreator(
String className,
ClassInfo info,
List<String> innerTypes,
Map<String, ClassInfo> globalClassLookup,
void Function(String) warn,
) {
if (info.typeParameters.length != 1) {
warn(
'Re-creator $className: expected exactly 1 type parameter, found '
'${info.typeParameters.length} — skipping',
);
return null;
}
final typeParam = info.typeParameters.keys.first;
ConstructorInfo? ctor;
for (final c in info.constructors) {
if (c.name == null) {
ctor = c;
break;
}
}
if (ctor == null) {
warn('Re-creator $className: no default (unnamed) constructor — skipping');
return null;
}
final getterNames = info
.allInstanceGetters(globalClassLookup)
.map((m) => m.name)
.toSet();
String? typeParamField;
var typeParamNullable = false;
for (final p in ctor.parameters) {
if (!getterNames.contains(p.name)) {
warn(
'Re-creator $className: constructor parameter "${p.name}" has no '
'matching instance getter — skipping',
);
return null;
}
final stripped = _stripNullableSuffix(p.type).trim();
if (stripped == typeParam) {
if (typeParamField != null) {
warn(
'Re-creator $className: multiple parameters reference type '
'parameter $typeParam — skipping',
);
return null;
}
typeParamField = p.name;
typeParamNullable = p.type.trim().endsWith('?');
} else if (_referencesTypeParam(p.type, typeParam)) {
warn(
'Re-creator $className: parameter "${p.name}" references type '
'parameter $typeParam in a non-bare form (${p.type}) — skipping',
);
return null;
}
}
final b = StringBuffer();
b.writeln(" D4.registerGenericTypeWrapper('$className', (");
b.writeln(' Object value,');
b.writeln(' String innerTypeArg,');
b.writeln(' ) {');
b.writeln(' if (value is! $className) return null;');
b.writeln(' final v = value;');
b.writeln(' return switch (innerTypeArg) {');
_writeReCreatorArm(
b,
className,
'dynamic',
"'dynamic' || 'Object' || 'Object?'",
ctor.parameters,
typeParamField,
null,
);
for (final inner in innerTypes) {
_writeReCreatorArm(
b,
className,
inner,
"'$inner'",
ctor.parameters,
typeParamField,
typeParamNullable ? '$inner?' : inner,
);
}
b.writeln(' _ => null,');
b.writeln(' };');
b.writeln(' });');
return b.toString();
}