Skip to content

Commit

Permalink
Add support for macros adding mixins and interfaces to existing types in
Browse files Browse the repository at this point in the history
the types phase.

Also seales the Code types hierarchy to block users from attempting to
create their own subclasses.

Bug: dart-lang/language#3211
Change-Id: I51ebdfdad6e1fba3c19ef91f25e238730e98e740
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/314821
Auto-Submit: Jake Macdonald <jakemac@google.com>
Reviewed-by: Bob Nystrom <rnystrom@google.com>
Commit-Queue: Jake Macdonald <jakemac@google.com>
  • Loading branch information
jakemac53 authored and Commit Queue committed Jul 21, 2023
1 parent 82a1963 commit 9803ffe
Show file tree
Hide file tree
Showing 16 changed files with 635 additions and 146 deletions.
28 changes: 28 additions & 0 deletions pkg/_fe_analyzer_shared/lib/src/macros/api/builders.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,34 @@ abstract interface class TypeBuilder implements Builder, TypePhaseIntrospector {
void declareType(String name, DeclarationCode typeDeclaration);
}

/// The API used by macros in the type phase to add interfaces to an existing
/// type.
abstract interface class InterfaceTypesBuilder implements TypeBuilder {
/// Appends [interfaces] to the list of interfaces for this type.
void appendInterfaces(Iterable<TypeAnnotationCode> interfaces);
}

/// The API used by macros in the type phase to add mixins to an existing
/// type.
abstract interface class MixinTypesBuilder implements TypeBuilder {
/// Appends [mixins] to the list of mixins for this type.
void appendMixins(Iterable<TypeAnnotationCode> mixins);
}

/// The API used by macros in the type phase to augment classes.
abstract interface class ClassTypeBuilder
implements TypeBuilder, InterfaceTypesBuilder, MixinTypesBuilder {}

/// The API used by macros in the type phase to augment enums.
abstract interface class EnumTypeBuilder
implements TypeBuilder, InterfaceTypesBuilder, MixinTypesBuilder {}

/// The API used by macros in the type phase to augment mixins.
///
/// Note that mixins don't support mixins, only interfaces.
abstract interface class MixinTypeBuilder
implements TypeBuilder, InterfaceTypesBuilder {}

/// The interface for all introspection that is allowed during the declaration
/// phase (and later).
abstract interface class DeclarationPhaseIntrospector
Expand Down
129 changes: 113 additions & 16 deletions pkg/_fe_analyzer_shared/lib/src/macros/api/code.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ sealed class Code {

/// An arbitrary chunk of code, which does not have to be syntactically valid
/// on its own. Useful to construct other types of code from several parts.
base class RawCode extends Code {
final class RawCode extends Code {
@override
CodeKind get kind => CodeKind.raw;

Expand All @@ -34,7 +34,7 @@ base class RawCode extends Code {
}

/// A piece of code representing a syntactically valid declaration.
base class DeclarationCode extends Code {
final class DeclarationCode extends Code {
@override
CodeKind get kind => CodeKind.declaration;

Expand All @@ -45,7 +45,7 @@ base class DeclarationCode extends Code {

/// A piece of code representing a code comment. This may contain identifier
/// references inside of `[]` brackets if the comments are doc comments.
base class CommentCode extends Code {
final class CommentCode extends Code {
@override
CodeKind get kind => CodeKind.comment;

Expand All @@ -55,7 +55,7 @@ base class CommentCode extends Code {
}

/// A piece of code representing a syntactically valid expression.
base class ExpressionCode extends Code {
final class ExpressionCode extends Code {
@override
CodeKind get kind => CodeKind.expression;

Expand All @@ -70,7 +70,7 @@ base class ExpressionCode extends Code {
/// including modifiers like `async`.
///
/// Both arrow and block function bodies are allowed.
base class FunctionBodyCode extends Code {
final class FunctionBodyCode extends Code {
@override
CodeKind get kind => CodeKind.functionBody;

Expand All @@ -90,7 +90,7 @@ base class FunctionBodyCode extends Code {
///
/// It is the job of the user to construct and combine these together in a way
/// that creates valid parameter lists.
base class ParameterCode implements Code {
final class ParameterCode implements Code {
final Code? defaultValue;
final List<String> keywords;
final String? name;
Expand Down Expand Up @@ -125,7 +125,7 @@ base class ParameterCode implements Code {
}

/// A piece of code representing a type annotation.
abstract class TypeAnnotationCode implements Code, TypeAnnotation {
sealed class TypeAnnotationCode implements Code, TypeAnnotation {
@override
TypeAnnotationCode get code => this;

Expand All @@ -148,7 +148,7 @@ abstract class TypeAnnotationCode implements Code, TypeAnnotation {
}

/// The nullable version of an underlying type annotation.
base class NullableTypeAnnotationCode implements TypeAnnotationCode {
final class NullableTypeAnnotationCode implements TypeAnnotationCode {
/// The underlying type that is being made nullable.
TypeAnnotationCode underlyingType;

Expand Down Expand Up @@ -178,7 +178,7 @@ base class NullableTypeAnnotationCode implements TypeAnnotationCode {
}

/// A piece of code representing a reference to a named type.
base class NamedTypeAnnotationCode extends TypeAnnotationCode {
final class NamedTypeAnnotationCode extends TypeAnnotationCode {
final Identifier name;

final List<TypeAnnotationCode> typeArguments;
Expand All @@ -200,7 +200,7 @@ base class NamedTypeAnnotationCode extends TypeAnnotationCode {
}

/// A piece of code representing a function type annotation.
base class FunctionTypeAnnotationCode extends TypeAnnotationCode {
final class FunctionTypeAnnotationCode extends TypeAnnotationCode {
final List<ParameterCode> namedParameters;

final List<ParameterCode> positionalParameters;
Expand Down Expand Up @@ -254,7 +254,7 @@ base class FunctionTypeAnnotationCode extends TypeAnnotationCode {
///
/// It is the job of the user to construct and combine these together in a way
/// that creates valid record type annotations.
base class RecordFieldCode implements Code {
final class RecordFieldCode implements Code {
final String? name;
final TypeAnnotationCode type;

Expand All @@ -274,7 +274,7 @@ base class RecordFieldCode implements Code {
}

/// A piece of code representing a syntactically valid record type annotation.
base class RecordTypeAnnotationCode extends TypeAnnotationCode {
final class RecordTypeAnnotationCode extends TypeAnnotationCode {
final List<RecordFieldCode> namedFields;

final List<RecordFieldCode> positionalFields;
Expand Down Expand Up @@ -308,7 +308,7 @@ base class RecordTypeAnnotationCode extends TypeAnnotationCode {
});
}

base class OmittedTypeAnnotationCode extends TypeAnnotationCode {
final class OmittedTypeAnnotationCode extends TypeAnnotationCode {
final OmittedTypeAnnotation typeAnnotation;

OmittedTypeAnnotationCode(this.typeAnnotation);
Expand All @@ -320,8 +320,102 @@ base class OmittedTypeAnnotationCode extends TypeAnnotationCode {
List<Object> get parts => [typeAnnotation];
}

/// Raw type annotations are typically used to refer to a local type which you
/// do not have an [Identifier] for (possibly you just created it).
///
/// Whenever possible, use a more specific [TypeAnnotationCode] subtype.
final class RawTypeAnnotationCode extends RawCode
implements TypeAnnotationCode {
@override
CodeKind get kind => CodeKind.rawTypeAnnotation;

/// Returns a [TypeAnnotationCode] object which is a non-nullable version
/// of this one.
///
/// Returns the current instance if it is already non-nullable.
@override
TypeAnnotationCode get asNonNullable => this;

/// Returns a [TypeAnnotationCode] object which is a non-nullable version
/// of this one.
///
/// Returns the current instance if it is already nullable.
@override
NullableTypeAnnotationCode get asNullable =>
new NullableTypeAnnotationCode(this);

RawTypeAnnotationCode._(super.parts) : super.fromParts();

/// Creates a [TypeAnnotationCode] from a raw [String].
///
/// The [code] object must not have trailing whitespace.
static TypeAnnotationCode fromString(String code) => fromParts([code]);

/// Creates a [TypeAnnotationCode] from a raw code [parts].
///
/// Must not end in trailing whitespace.
static TypeAnnotationCode fromParts(List<Object> parts) {
bool wasNullable;
(wasNullable, parts) = _makeNonNullable(parts);
TypeAnnotationCode code = new RawTypeAnnotationCode._(parts);
if (wasNullable) code = code.asNullable;
return code;
}

@override
TypeAnnotationCode get code => this;

@override
bool get isNullable => false;

/// Checks if [parts] ends with a ?, and if so then it is removed.
///
/// Returns a record which indicates if [parts] was nullable originally, as
/// well as the potentially new list of parts.
///
/// Throws if [parts] ends with whitespace because we don't allow type
/// annotations to do that.
static (bool wasNullable, List<Object> parts) _makeNonNullable(
List<Object> parts) {
final Iterator<Object> iterator = parts.reversed.iterator;
while (iterator.moveNext()) {
final Object current = iterator.current;
switch (current) {
case String():
if (current.trimRight() != current) {
throw new ArgumentError(
'Invalid type annotation, type annotations should not end with '
'whitespace but got `$current`.');
} else if (current.isEmpty) {
continue;
} else if (current.endsWith('?')) {
// It was nullable, trim the `?` and return a copy.
return (
true,
// We are iterating backwards, and need to reverse it after.
[
// Strip the '?'.
current.substring(0, current.length - 1),
for (bool hasNext = iterator.moveNext();
hasNext;
hasNext = iterator.moveNext())
iterator.current,
].reversed.toList(),
);
} else {
return (false, parts);
}
case Identifier():
// Identifiers never contain a `?`.
return (false, parts);
}
}
throw new ArgumentError('The empty string is not a valid type annotation.');
}
}

/// A piece of code representing a valid named type parameter.
base class TypeParameterCode implements Code {
final class TypeParameterCode implements Code {
final TypeAnnotationCode? bound;
final String name;

Expand All @@ -341,8 +435,10 @@ base class TypeParameterCode implements Code {
}

extension Join<T extends Object> on List<T> {
/// Joins all the items in [this] with [separator], and returns
/// a new list.
/// Joins all the items in [this] with [separator], and returns a new list.
///
/// Works on any kind of non-nullable list which accepts String entries, and
/// does not convert the individual items to strings.
List<Object> joinAsCode(String separator) => [
for (int i = 0; i < length - 1; i++) ...[
this[i],
Expand All @@ -363,6 +459,7 @@ enum CodeKind {
omittedTypeAnnotation,
parameter,
raw,
rawTypeAnnotation,
recordField,
recordTypeAnnotation,
typeParameter,
Expand Down
7 changes: 4 additions & 3 deletions pkg/_fe_analyzer_shared/lib/src/macros/api/macros.dart
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ abstract interface class VariableDefinitionMacro implements Macro {
/// contribute new type declarations to the program.
abstract interface class ClassTypesMacro implements Macro {
FutureOr<void> buildTypesForClass(
ClassDeclaration clazz, TypeBuilder builder);
ClassDeclaration clazz, ClassTypeBuilder builder);
}

/// The interface for [Macro]s that can be applied to any class, and want to
Expand All @@ -98,7 +98,8 @@ abstract interface class ClassDefinitionMacro implements Macro {
/// The interface for [Macro]s that can be applied to any enum, and want to
/// contribute new type declarations to the program.
abstract interface class EnumTypesMacro implements Macro {
FutureOr<void> buildTypesForEnum(EnumDeclaration enuum, TypeBuilder builder);
FutureOr<void> buildTypesForEnum(
EnumDeclaration enuum, EnumTypeBuilder builder);
}

/// The interface for [Macro]s that can be applied to any enum, and want to
Expand Down Expand Up @@ -203,7 +204,7 @@ abstract interface class ConstructorDefinitionMacro implements Macro {
/// want to contribute new type declarations to the program.
abstract interface class MixinTypesMacro implements Macro {
FutureOr<void> buildTypesForMixin(
MixinDeclaration mixin, TypeBuilder builder);
MixinDeclaration mixin, MixinTypeBuilder builder);
}

/// The interface for [Macro]s that can be applied to any mixin declaration, and
Expand Down
8 changes: 8 additions & 0 deletions pkg/_fe_analyzer_shared/lib/src/macros/executor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -159,10 +159,18 @@ abstract class MacroExecutionResult implements Serializable {
/// result of executing a macro, indexed by the identifier of the enum.
Map<Identifier, Iterable<DeclarationCode>> get enumValueAugmentations;

/// Any interfaces that should be added to types as a result of executing a
/// macro, indexed by the identifier of the augmented type declaration.
Map<Identifier, Iterable<TypeAnnotationCode>> get interfaceAugmentations;

/// Any augmentations that should be applied to the library as a result of
/// executing a macro.
Iterable<DeclarationCode> get libraryAugmentations;

/// Any mixins that should be added to types as a result of executing a macro,
/// indexed by the identifier of the augmented type declaration.
Map<Identifier, Iterable<TypeAnnotationCode>> get mixinAugmentations;

/// The names of any new types declared in [augmentations].
Iterable<String> get newTypeNames;

Expand Down
24 changes: 6 additions & 18 deletions pkg/_fe_analyzer_shared/lib/src/macros/executor/arguments.dart
Original file line number Diff line number Diff line change
Expand Up @@ -262,18 +262,14 @@ abstract base class _IterableArgument<T extends Iterable<Object?>>
..moveNext()
..expectList();
final List<ArgumentKind> typeArguments = [
for (bool hasNext = deserializer.moveNext();
hasNext;
hasNext = deserializer.moveNext())
for (; deserializer.moveNext();)
ArgumentKind.values[deserializer.expectInt()],
];
deserializer
..moveNext()
..expectList();
final List<Argument> values = [
for (bool hasNext = deserializer.moveNext();
hasNext;
hasNext = deserializer.moveNext())
for (; deserializer.moveNext();)
new Argument.deserialize(deserializer, alreadyMoved: true),
];
return switch (kind) {
Expand Down Expand Up @@ -361,18 +357,14 @@ final class MapArgument extends _CollectionArgument {
..moveNext()
..expectList();
final List<ArgumentKind> typeArguments = [
for (bool hasNext = deserializer.moveNext();
hasNext;
hasNext = deserializer.moveNext())
for (; deserializer.moveNext();)
ArgumentKind.values[deserializer.expectInt()],
];
deserializer
..moveNext()
..expectList();
final Map<Argument, Argument> arguments = {
for (bool hasNext = deserializer.moveNext();
hasNext;
hasNext = deserializer.moveNext())
for (; deserializer.moveNext();)
new Argument.deserialize(deserializer, alreadyMoved: true):
new Argument.deserialize(deserializer),
};
Expand Down Expand Up @@ -407,18 +399,14 @@ class Arguments implements Serializable {
..moveNext()
..expectList();
final List<Argument> positionalArgs = [
for (bool hasNext = deserializer.moveNext();
hasNext;
hasNext = deserializer.moveNext())
for (; deserializer.moveNext();)
new Argument.deserialize(deserializer, alreadyMoved: true),
];
deserializer
..moveNext()
..expectList();
final Map<String, Argument> namedArgs = {
for (bool hasNext = deserializer.moveNext();
hasNext;
hasNext = deserializer.moveNext())
for (; deserializer.moveNext();)
deserializer.expectString(): new Argument.deserialize(deserializer),
};
return new Arguments(positionalArgs, namedArgs);
Expand Down
Loading

0 comments on commit 9803ffe

Please sign in to comment.