Skip to content

Commit

Permalink
add InheritedWidget macro example and fix up FunctionalWidget (#3256)
Browse files Browse the repository at this point in the history
* add InheritedWidget macro example and fix up FunctionalWidget

* update to latest apis
  • Loading branch information
jakemac53 authored Jan 5, 2024
1 parent 757accf commit daf5cbd
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 10 deletions.
5 changes: 4 additions & 1 deletion working/macros/example/benchmark/src/functional_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ Future<void> runBenchmarks(MacroExecutor executor, Uri macroUri) async {
'int': intIdentifier,
'String': stringIdentifier,
},
Uri.parse('package:flutter/flutter.dart'): {
Uri.parse('package:flutter/widgets.dart'): {
'BuildContext': buildContextIdentifier,
'StatelessWidget': statelessWidgetIdentifier,
'Widget': widgetIdentifier,
}
});
Expand Down Expand Up @@ -67,6 +68,8 @@ class FunctionalWidgetTypesPhaseBenchmark extends AsyncBenchmarkBase {

final buildContextIdentifier =
IdentifierImpl(id: RemoteInstance.uniqueId, name: 'BuildContext');
final statelessWidgetIdentifier =
IdentifierImpl(id: RemoteInstance.uniqueId, name: 'StatelessWidget');
final buildContextType = NamedTypeAnnotationImpl(
id: RemoteInstance.uniqueId,
isNullable: false,
Expand Down
36 changes: 27 additions & 9 deletions working/macros/example/lib/functional_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@
// There is no public API exposed yet, the in-progress API lives here.
import 'package:_fe_analyzer_shared/src/macros/api.dart';

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

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

@override
void buildTypesForFunction(
FunctionDeclaration function, TypeBuilder builder) {
Future<void> buildTypesForFunction(
FunctionDeclaration function, TypeBuilder builder) async {
if (!function.identifier.name.startsWith('_')) {
throw ArgumentError(
'FunctionalWidget should only be used on private declarations');
Expand All @@ -36,10 +44,19 @@ macro class FunctionalWidget implements FunctionTypesMacro {
function.identifier.name
.replaceRange(0, 2, function.identifier.name[1].toUpperCase());
var positionalFieldParams = function.positionalParameters.skip(1);
// ignore: deprecated_member_use
var statelessWidget = await builder.resolveIdentifier(
Uri.parse('package:flutter/widgets.dart'), 'StatelessWidget');
// ignore: deprecated_member_use
var buildContext = await builder.resolveIdentifier(
Uri.parse('package:flutter/widgets.dart'), 'BuildContext');
// ignore: deprecated_member_use
var widget = await builder.resolveIdentifier(
Uri.parse('package:flutter/widgets.dart'), 'Widget');
builder.declareType(
widgetName,
DeclarationCode.fromParts([
'class $widgetName extends StatelessWidget {',
'class $widgetName extends ', statelessWidget, ' {',
// Fields
for (var param
in positionalFieldParams.followedBy(function.namedParameters))
Expand All @@ -57,13 +74,14 @@ macro class FunctionalWidget implements FunctionTypesMacro {
'{',
for (var param in function.namedParameters)
'${param.isRequired ? 'required ' : ''}this.${param.identifier.name}, ',
'Key? key,',
'}',
') : super(key: key);',
'super.key,',
'});',
// Build method
'''
@override
Widget build(BuildContext context) => ''',
'@override ',
widget,
' build(',
buildContext,
' context) => ',
function.identifier,
'(context, ',
for (var param in positionalFieldParams) '${param.identifier.name}, ',
Expand Down
97 changes: 97 additions & 0 deletions working/macros/example/lib/inherited_widget.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

// There is no public API exposed yet, the in-progress API lives here.
import 'package:_fe_analyzer_shared/src/macros/api.dart';

/// A macro that annotates a class and turns it into an inherited widget.
///
/// This will fill in any "holes" that do not have custom implementations,
/// specifically the following items will be added if they don't exist:
///
/// - Make the class extend `InheritedWidget`.
/// - Add a constructor that will initialize any fields that are defined, and
/// take `key` and `child` parameters which it forwards to the super
/// constructor.
/// - Add static `of` and `maybeOf` methods which take a build context and
/// return an instance of this class using `dependOnIheritedWidgetOfExactType`.
/// - Add an `updateShouldNotify` method which does checks for equality of all
/// fields.
macro class InheritedWidget implements ClassTypesMacro, ClassDeclarationsMacro {
const InheritedWidget();

@override
void buildTypesForClass(
ClassDeclaration clazz, ClassTypeBuilder builder) {
if (clazz.superclass != null) return;
// TODO: Add `extends InheritedWidget` once we have an API for that.
}

@override
Future<void> buildDeclarationsForClass(ClassDeclaration clazz, MemberDeclarationBuilder builder) async {
final fields = await builder.fieldsOf(clazz);
final methods = await builder.methodsOf(clazz);
if (!methods.any((method) => method is ConstructorDeclaration)) {
builder.declareInType(DeclarationCode.fromParts([
'const ${clazz.identifier.name}(',
'{',
for (var field in fields)...[
field.type.isNullable ? '' : 'required ',
field.identifier,
',',
],
'super.key,',
'required super.child,',
'});',
]));
}

final buildContext =
// ignore: deprecated_member_use
await builder.resolveIdentifier(Uri.parse('package:flutter/widgets.dart'), 'BuildContext');
if (!methods.any((method) => method.identifier.name == "maybeOf")) {
builder.declareInType(DeclarationCode.fromParts([
'static ',
clazz.identifier,
'? maybeOf(',
buildContext,
' context) => context.dependOnInheritedWidgetOfExactType<',
clazz.identifier,
'>();',
]));
}

if (!methods.any((method) => method.identifier.name == "of")) {
builder.declareInType(DeclarationCode.fromParts([
'static ',
clazz.identifier,
' of(',
buildContext,
''' context) {
final result = this.maybeOf(context);
assert(result != null, 'No ${clazz.identifier.name} found in context');
return result!;
}''',
]));
}

if (!methods.any((method) => method.identifier.name == 'updateShouldNotify')) {
// ignore: deprecated_member_use
final override = await builder.resolveIdentifier(
Uri.parse('package:meta/meta.dart'), 'override');
builder.declareInType(DeclarationCode.fromParts([
'@',
override,
' bool updateShouldNotify(',
clazz.identifier,
' oldWidget) =>',
...[
for (var field in fields)
'oldWidget.${field.identifier.name} != this.${field.identifier.name}',
].joinAsCode(' || '),
';',
]));
}
}
}

0 comments on commit daf5cbd

Please sign in to comment.