diff --git a/test/mustachio/builder_test.dart b/test/mustachio/builder_test.dart index 18f58a7a4c..0dcf6a8374 100644 --- a/test/mustachio/builder_test.dart +++ b/test/mustachio/builder_test.dart @@ -25,7 +25,7 @@ class Context { }; const _libraryFrontMatter = ''' -@Renderer(#renderFoo, Context(), visibleTypes: {Bar}) +@Renderer(#renderFoo, Context(), visibleTypes: {Bar, Baz}) library foo; import 'package:mustachio/annotations.dart'; '''; @@ -73,15 +73,19 @@ $sourceLibraryContent setUpAll(() async { writer = InMemoryAssetWriter(); await testMustachioBuilder(''' -abstract class FooBase { +abstract class FooBase2 { + T get generic; +} +abstract class FooBase extends FooBase2 { Bar get bar; } -class Foo extends FooBase { +abstract class Foo extends FooBase { String s1 = "s1"; bool b1 = false; List l1 = [1, 2, 3]; } class Bar {} +class Baz {} '''); renderersLibrary = await resolveGeneratedLibrary(writer); var rendererAsset = AssetId.parse('foo|lib/foo.renderers.dart'); @@ -92,11 +96,13 @@ class Bar {} // The render function for Foo expect( generatedContent, - contains('String _render_FooBase(\n' - ' FooBase context, List ast, Template template,')); + contains('String _render_FooBase(\n' + ' FooBase context, List ast, Template template,')); // The renderer class for Foo - expect(generatedContent, - contains('class _Renderer_FooBase extends RendererBase')); + expect( + generatedContent, + contains( + 'class _Renderer_FooBase extends RendererBase>')); }); test('for Object', () { @@ -121,6 +127,11 @@ class Bar {} expect(renderersLibrary.getType('_Renderer_Bar'), isNotNull); }); + test('for a generic, bounded type found in a getter', () { + expect(renderersLibrary.getTopLevelFunction('_render_Baz'), isNotNull); + expect(renderersLibrary.getType('_Renderer_Baz'), isNotNull); + }); + test('with a property map', () { expect( generatedContent, @@ -130,7 +141,7 @@ class Bar {} test('with a property map which references the superclass', () { expect(generatedContent, - contains('..._Renderer_FooBase.propertyMap(),')); + contains('..._Renderer_FooBase.propertyMap(),')); }); test('with a property map with a bool property', () { @@ -203,6 +214,7 @@ class Bar {} await testMustachioBuilder(''' class Foo {} class Bar {} +class Baz {} ''', libraryFrontMatter: ''' @Renderer(#renderFoo, Context()) @Renderer(#renderBar, Context()) @@ -229,6 +241,7 @@ class FooBase {} class Foo extends FooBase {} class BarBase {} class Bar extends BarBase {} +class Baz {} ''', libraryFrontMatter: ''' @Renderer(#renderFoo, Context()) @Renderer(#renderBar, Context()) @@ -275,6 +288,7 @@ import 'package:mustachio/annotations.dart'; await testMustachioBuilder(''' class Foo {} class Bar {} +class Baz {} '''); var renderersLibrary = await resolveGeneratedLibrary(writer); @@ -295,13 +309,14 @@ class Bar {} setUpAll(() async { writer = InMemoryAssetWriter(); await testMustachioBuilder(''' -class Foo { +abstract class Foo { static Static get static1 => Bar(); Private get _private1 => Bar(); void set setter1(Setter s); Method method1(Method m); } class Bar {} +class Baz {} class Static {} class Private {} class Setter {} diff --git a/tool/mustachio/codegen_runtime_renderer.dart b/tool/mustachio/codegen_runtime_renderer.dart index 8e930f07b4..9e85bd56f1 100644 --- a/tool/mustachio/codegen_runtime_renderer.dart +++ b/tool/mustachio/codegen_runtime_renderer.dart @@ -101,11 +101,16 @@ String _simpleResolveErrorMessage(List key, String type) => specs.forEach(_addTypesForRendererSpec); + var builtRenderers = {}; + while (_typesToProcess.isNotEmpty) { var info = _typesToProcess.removeFirst(); if (info.isFullRenderer) { - _buildRenderer(info); + var buildOnlyPublicFunction = + builtRenderers.contains(info._contextClass); + _buildRenderer(info, buildOnlyPublicFunction: buildOnlyPublicFunction); + builtRenderers.add(info._contextClass); } } @@ -142,6 +147,18 @@ String _simpleResolveErrorMessage(List key, String type) => if (property.isPrivate || property.isStatic || property.isSetter) return; if (property.hasProtected || property.hasVisibleForTesting) return; var type = property.type.returnType; + if (type is TypeParameterType) { + var bound = (type as TypeParameterType).bound; + if (bound == null || bound.isDynamic) { + // Don't add functions for a generic type, for example + // `List.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. + return; + } + type = bound; + } var isFullRenderer = _isVisibleToMustache(type.element); if (_typeSystem.isAssignableTo(type, _typeProvider.iterableDynamicType)) { @@ -170,9 +187,8 @@ String _simpleResolveErrorMessage(List key, String type) => /// Adds [type] to the [_typesToProcess] queue, if it is not already there. void _addTypeToProcess(ClassElement element, {@required isFullRenderer}) { - var typeToProcess = _typesToProcess - .singleWhere((rs) => rs._contextClass == element, orElse: () => null); - if (typeToProcess == null) { + var types = _typesToProcess.where((rs) => rs._contextClass == element); + if (types.isEmpty) { var rendererInfo = _RendererInfo(element, isFullRenderer: isFullRenderer, public: _rendererClassesArePublic); _typesToProcess.add(rendererInfo); @@ -181,11 +197,14 @@ String _simpleResolveErrorMessage(List key, String type) => _typeToRendererClassName[element] = rendererInfo._rendererClassName; } } else { - if (isFullRenderer && !typeToProcess.isFullRenderer) { - // This is the only case in which we update a type-to-render. - typeToProcess.isFullRenderer = true; - _typeToRenderFunctionName[element] = typeToProcess._renderFunctionName; - _typeToRendererClassName[element] = typeToProcess._rendererClassName; + for (var typeToProcess in types) { + if (isFullRenderer && !typeToProcess.isFullRenderer) { + // This is the only case in which we update a type-to-render. + typeToProcess.isFullRenderer = true; + _typeToRenderFunctionName[element] = + typeToProcess._renderFunctionName; + _typeToRendererClassName[element] = typeToProcess._rendererClassName; + } } } } @@ -201,14 +220,19 @@ String _simpleResolveErrorMessage(List key, String type) => return _isVisibleToMustache(element.supertype.element); } - /// Builds both the render function and the renderer class for [renderer]. + /// Builds render functions and the renderer class for [renderer]. /// /// The function and the class are each written as Dart code to [_buffer]. /// /// If [renderer] also specifies a `publicApiFunctionName`, then a public API /// function (which renders a context object using a template file at a path, /// rather than an AST) is also written. - void _buildRenderer(_RendererInfo renderer) { + /// + /// If [buildOnlyPublicFunction] is true, then the private render function and + /// renderer classes are not built, having been built for a different + /// [_RendererInfo]. + void _buildRenderer(_RendererInfo renderer, + {@required bool buildOnlyPublicFunction}) { var typeName = renderer._typeName; var typeWithVariables = '$typeName${renderer._typeVariablesString}'; @@ -221,6 +245,8 @@ String ${renderer.publicApiFunctionName}${renderer._typeParametersString}( '''); } + if (buildOnlyPublicFunction) return; + // Write out the render function. _buffer.writeln(''' String ${renderer._renderFunctionName}${renderer._typeParametersString}(