in tool/mustachio/codegen_runtime_renderer.dart [403:595]
void _writePropertyMap(_RendererInfo renderer) {
var contextClass = renderer._contextClass;
var generics = renderer._typeParametersStringWith(
'$_contextTypeVariable extends ${renderer._typeName}');
// It would be simplest if [propertyMap] were just a getter, but it must be
// parameterized on `CT_`, so it is a static method. Due to the possibly
// extensive amount of spreading (supertypes, mixins) and object
// construction (lots of [Property] objects with function literals), we
// cache the construction of each one, keyed to the `CT_` value. Each cache
// should not have many entries, as there are probably not many values for
// each type variable, `CT_`, typically one.
_buffer.writeln('''
static final Map<Type, Object> _propertyMapCache = {};
static Map<String, Property<$_contextTypeVariable>> propertyMap$generics() =>
_propertyMapCache.putIfAbsent($_contextTypeVariable, () => {''');
var supertype = contextClass.supertype;
if (supertype != null) {
var superclassRendererName = _typeToRendererClassName[supertype.element];
if (superclassRendererName != null) {
var superMapName = '$superclassRendererName.propertyMap';
var generics = asGenerics([
...supertype.typeArguments
.map((e) => e.getDisplayString(withNullability: false)),
_contextTypeVariable
]);
_buffer.writeln(' ...$superMapName$generics(),');
}
}
// Mixins are spread into the property map _after_ the super class, so
// that they override any values which need to be overridden. Superclass
// and mixins override from left to right, as do spreads:
// `class C extends E with M, N` first takes members from N, then M, then
// E. Similarly, `{...a, ...b, ...c}` will feature elements from `c` which
// override `b` and `a`.
for (var mixin in contextClass.mixins) {
var mixinRendererName = _typeToRendererClassName[mixin.element];
if (mixinRendererName != null) {
var mixinMapName = '$mixinRendererName.propertyMap';
var generics = asGenerics([
...mixin.typeArguments
.map((e) => e.getDisplayString(withNullability: false)),
_contextTypeVariable
]);
_buffer.writeln(' ...$mixinMapName$generics(),');
}
}
for (var property in [...contextClass.accessors]
..sort((a, b) => a.name.compareTo(b.name))) {
var returnType = property.type.returnType;
if (returnType is InterfaceType) {
_writeProperty(renderer, property, returnType);
} else if (returnType is TypeParameterType &&
!returnType.bound.isDynamic) {
_writeProperty(renderer, property, returnType.bound as InterfaceType);
}
}
_buffer.writeln('}) as Map<String, Property<$_contextTypeVariable>>;');
_buffer.writeln('');
}
void _writeProperty(_RendererInfo renderer, PropertyAccessorElement property,
InterfaceType getterType) {
if (getterType == _typeProvider.typeType) {
// The [Type] type is the first case of a type we don't want to traverse.
return;
}
if (property.isPrivate || property.isStatic || property.isSetter) return;
if (property.hasProtected || property.hasVisibleForTesting) return;
_buffer.writeln("'${property.name}': Property(");
_buffer
.writeln('getValue: ($_contextTypeVariable c) => c.${property.name},');
var getterName = property.name;
var getterTypeString = getterType.getDisplayString(withNullability: false);
// Only add a `getProperties` function, which returns the property map for
// [getterType], if [getterType] is a renderable type.
if (_typeToRendererClassName.containsKey(getterType.element)) {
var rendererClassName = _typeToRendererClassName[getterType.element];
_buffer.writeln('''
renderVariable:
($_contextTypeVariable c, Property<$_contextTypeVariable> self, List<String> remainingNames) {
if (remainingNames.isEmpty) {
return self.getValue(c).toString();
}
var name = remainingNames.first;
var nextProperty = $rendererClassName.propertyMap().getValue(name);
return nextProperty.renderVariable(
self.getValue(c) as $getterTypeString, nextProperty, [...remainingNames.skip(1)]);
},
''');
} else {
// [getterType] does not have a full renderer, so we just render a simple
// variable, with no opportunity to access fields on [getterType].
_buffer.writeln('''
renderVariable:
($_contextTypeVariable c, Property<CT_> self, List<String> remainingNames) =>
self.renderSimpleVariable(c, remainingNames, '$getterTypeString'),
''');
}
if (getterType.isDartCoreBool) {
_buffer.writeln(
'getBool: ($_contextTypeVariable c) => c.$getterName == true,');
} else if (_typeSystem.isAssignableTo(
getterType, _typeProvider.iterableDynamicType)) {
var iterableElement = _typeProvider.iterableElement;
var iterableType = getterType.asInstanceOf(iterableElement);
// Not sure why [iterableType] would be null... unresolved type?
if (iterableType != null) {
var innerType = iterableType.typeArguments.first;
// Don't add Iterable functions for a generic type, for example
// `List<E>.reversed` has inner type `E`, which we don't have a specific
// renderer for.
// TODO(srawlins): Find a solution for this. We can track all of the
// concrete types substituted for `E` for example.
if (innerType is! TypeParameterType) {
var renderFunctionName = _typeToRenderFunctionName[innerType.element];
String renderCall;
if (renderFunctionName == null) {
var typeName = innerType.element!.name!;
if (innerType is InterfaceType) {
_invisibleGetters.putIfAbsent(
typeName, () => innerType.element.allAccessorNames);
}
renderCall = 'renderSimple(e, ast, r.template, sink, parent: r, '
"getters: _invisibleGetters['$typeName']!)";
} else {
var bang = _typeSystem.isPotentiallyNullable(innerType) ? '!' : '';
renderCall =
'$renderFunctionName(e$bang, ast, r.template, sink, parent: r)';
}
_buffer.writeln('''
renderIterable:
($_contextTypeVariable c, RendererBase<$_contextTypeVariable> r,
List<MustachioNode> ast, StringSink sink) {
return c.$getterName.map((e) => $renderCall);
},
''');
}
}
} else {
// Don't add Iterable functions for a generic type, for example
// `List<E>.first` has type `E`, which we don't have a specific
// renderer for.
// TODO(srawlins): Find a solution for this. We can track all of the
// concrete types substituted for `E` for example.
if (getterName is! TypeParameterType) {
var renderFunctionName = _typeToRenderFunctionName[getterType.element];
String renderCall;
if (renderFunctionName == null) {
var typeName = getterType.element.name;
_invisibleGetters.putIfAbsent(
typeName, () => getterType.element.allAccessorNames);
renderCall =
'renderSimple(c.$getterName, ast, r.template, sink, parent: r, '
"getters: _invisibleGetters['$typeName']!)";
} else {
var bang = _typeSystem.isPotentiallyNullable(getterType) ? '!' : '';
renderCall =
'$renderFunctionName(c.$getterName$bang, ast, r.template, sink, parent: r)';
}
var nullValueGetter =
getterType.nullabilitySuffix == NullabilitySuffix.none
? 'false'
: 'c.$getterName == null';
_buffer.writeln('''
isNullValue: ($_contextTypeVariable c) => $nullValueGetter,
renderValue:
($_contextTypeVariable c, RendererBase<$_contextTypeVariable> r,
List<MustachioNode> ast, StringSink sink) {
$renderCall;
},
''');
}
}
_buffer.writeln('),');
}
/// Writes the mapping of invisible getters, used to report simple renderer
/// errors.
void _writeInvisibleGetters() {
_buffer.write('const _invisibleGetters = {');
for (var class_ in _invisibleGetters.keys.toList()..sort()) {
_buffer.write("'$class_':");
var getters = _invisibleGetters[class_]!.toList()..sort();
_buffer.write('{${getters.map((e) => "'$e'").join(', ')}},');
}
_buffer.write('};');
}
}