Skip to content

Commit daf5cbd

Browse files
authored
add InheritedWidget macro example and fix up FunctionalWidget (#3256)
* add InheritedWidget macro example and fix up FunctionalWidget * update to latest apis
1 parent 757accf commit daf5cbd

File tree

3 files changed

+128
-10
lines changed

3 files changed

+128
-10
lines changed

working/macros/example/benchmark/src/functional_widget.dart

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ Future<void> runBenchmarks(MacroExecutor executor, Uri macroUri) async {
1212
'int': intIdentifier,
1313
'String': stringIdentifier,
1414
},
15-
Uri.parse('package:flutter/flutter.dart'): {
15+
Uri.parse('package:flutter/widgets.dart'): {
1616
'BuildContext': buildContextIdentifier,
17+
'StatelessWidget': statelessWidgetIdentifier,
1718
'Widget': widgetIdentifier,
1819
}
1920
});
@@ -67,6 +68,8 @@ class FunctionalWidgetTypesPhaseBenchmark extends AsyncBenchmarkBase {
6768

6869
final buildContextIdentifier =
6970
IdentifierImpl(id: RemoteInstance.uniqueId, name: 'BuildContext');
71+
final statelessWidgetIdentifier =
72+
IdentifierImpl(id: RemoteInstance.uniqueId, name: 'StatelessWidget');
7073
final buildContextType = NamedTypeAnnotationImpl(
7174
id: RemoteInstance.uniqueId,
7275
isNullable: false,

working/macros/example/lib/functional_widget.dart

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,14 @@
55
// There is no public API exposed yet, the in-progress API lives here.
66
import 'package:_fe_analyzer_shared/src/macros/api.dart';
77

8+
/// A macro that annotates a function, which becomes the build method for a
9+
/// generated stateless widget.
10+
///
11+
/// The function must have at least one positional parameter, which is of type
12+
/// BuildContext (and this must be the first parameter).
13+
///
14+
/// Any additional function parameters are turned into fields on the stateless
15+
/// widget.
816
macro class FunctionalWidget implements FunctionTypesMacro {
917
final Identifier? widgetIdentifier;
1018

@@ -15,8 +23,8 @@ macro class FunctionalWidget implements FunctionTypesMacro {
1523
this.widgetIdentifier});
1624

1725
@override
18-
void buildTypesForFunction(
19-
FunctionDeclaration function, TypeBuilder builder) {
26+
Future<void> buildTypesForFunction(
27+
FunctionDeclaration function, TypeBuilder builder) async {
2028
if (!function.identifier.name.startsWith('_')) {
2129
throw ArgumentError(
2230
'FunctionalWidget should only be used on private declarations');
@@ -36,10 +44,19 @@ macro class FunctionalWidget implements FunctionTypesMacro {
3644
function.identifier.name
3745
.replaceRange(0, 2, function.identifier.name[1].toUpperCase());
3846
var positionalFieldParams = function.positionalParameters.skip(1);
47+
// ignore: deprecated_member_use
48+
var statelessWidget = await builder.resolveIdentifier(
49+
Uri.parse('package:flutter/widgets.dart'), 'StatelessWidget');
50+
// ignore: deprecated_member_use
51+
var buildContext = await builder.resolveIdentifier(
52+
Uri.parse('package:flutter/widgets.dart'), 'BuildContext');
53+
// ignore: deprecated_member_use
54+
var widget = await builder.resolveIdentifier(
55+
Uri.parse('package:flutter/widgets.dart'), 'Widget');
3956
builder.declareType(
4057
widgetName,
4158
DeclarationCode.fromParts([
42-
'class $widgetName extends StatelessWidget {',
59+
'class $widgetName extends ', statelessWidget, ' {',
4360
// Fields
4461
for (var param
4562
in positionalFieldParams.followedBy(function.namedParameters))
@@ -57,13 +74,14 @@ macro class FunctionalWidget implements FunctionTypesMacro {
5774
'{',
5875
for (var param in function.namedParameters)
5976
'${param.isRequired ? 'required ' : ''}this.${param.identifier.name}, ',
60-
'Key? key,',
61-
'}',
62-
') : super(key: key);',
77+
'super.key,',
78+
'});',
6379
// Build method
64-
'''
65-
@override
66-
Widget build(BuildContext context) => ''',
80+
'@override ',
81+
widget,
82+
' build(',
83+
buildContext,
84+
' context) => ',
6785
function.identifier,
6886
'(context, ',
6987
for (var param in positionalFieldParams) '${param.identifier.name}, ',
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
// There is no public API exposed yet, the in-progress API lives here.
6+
import 'package:_fe_analyzer_shared/src/macros/api.dart';
7+
8+
/// A macro that annotates a class and turns it into an inherited widget.
9+
///
10+
/// This will fill in any "holes" that do not have custom implementations,
11+
/// specifically the following items will be added if they don't exist:
12+
///
13+
/// - Make the class extend `InheritedWidget`.
14+
/// - Add a constructor that will initialize any fields that are defined, and
15+
/// take `key` and `child` parameters which it forwards to the super
16+
/// constructor.
17+
/// - Add static `of` and `maybeOf` methods which take a build context and
18+
/// return an instance of this class using `dependOnIheritedWidgetOfExactType`.
19+
/// - Add an `updateShouldNotify` method which does checks for equality of all
20+
/// fields.
21+
macro class InheritedWidget implements ClassTypesMacro, ClassDeclarationsMacro {
22+
const InheritedWidget();
23+
24+
@override
25+
void buildTypesForClass(
26+
ClassDeclaration clazz, ClassTypeBuilder builder) {
27+
if (clazz.superclass != null) return;
28+
// TODO: Add `extends InheritedWidget` once we have an API for that.
29+
}
30+
31+
@override
32+
Future<void> buildDeclarationsForClass(ClassDeclaration clazz, MemberDeclarationBuilder builder) async {
33+
final fields = await builder.fieldsOf(clazz);
34+
final methods = await builder.methodsOf(clazz);
35+
if (!methods.any((method) => method is ConstructorDeclaration)) {
36+
builder.declareInType(DeclarationCode.fromParts([
37+
'const ${clazz.identifier.name}(',
38+
'{',
39+
for (var field in fields)...[
40+
field.type.isNullable ? '' : 'required ',
41+
field.identifier,
42+
',',
43+
],
44+
'super.key,',
45+
'required super.child,',
46+
'});',
47+
]));
48+
}
49+
50+
final buildContext =
51+
// ignore: deprecated_member_use
52+
await builder.resolveIdentifier(Uri.parse('package:flutter/widgets.dart'), 'BuildContext');
53+
if (!methods.any((method) => method.identifier.name == "maybeOf")) {
54+
builder.declareInType(DeclarationCode.fromParts([
55+
'static ',
56+
clazz.identifier,
57+
'? maybeOf(',
58+
buildContext,
59+
' context) => context.dependOnInheritedWidgetOfExactType<',
60+
clazz.identifier,
61+
'>();',
62+
]));
63+
}
64+
65+
if (!methods.any((method) => method.identifier.name == "of")) {
66+
builder.declareInType(DeclarationCode.fromParts([
67+
'static ',
68+
clazz.identifier,
69+
' of(',
70+
buildContext,
71+
''' context) {
72+
final result = this.maybeOf(context);
73+
assert(result != null, 'No ${clazz.identifier.name} found in context');
74+
return result!;
75+
}''',
76+
]));
77+
}
78+
79+
if (!methods.any((method) => method.identifier.name == 'updateShouldNotify')) {
80+
// ignore: deprecated_member_use
81+
final override = await builder.resolveIdentifier(
82+
Uri.parse('package:meta/meta.dart'), 'override');
83+
builder.declareInType(DeclarationCode.fromParts([
84+
'@',
85+
override,
86+
' bool updateShouldNotify(',
87+
clazz.identifier,
88+
' oldWidget) =>',
89+
...[
90+
for (var field in fields)
91+
'oldWidget.${field.identifier.name} != this.${field.identifier.name}',
92+
].joinAsCode(' || '),
93+
';',
94+
]));
95+
}
96+
}
97+
}

0 commit comments

Comments
 (0)