Skip to content

Commit bd04424

Browse files
chloestefantsovaCommit Bot
authored and
Commit Bot
committed
[cfe] Allow mixins in enums
Part of #47453 Change-Id: I04d0a7690b6619ca61f364da756917319e6081b8 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/227541 Reviewed-by: Johnni Winther <[email protected]> Commit-Queue: Chloe Stefantsova <[email protected]>
1 parent a771d6e commit bd04424

18 files changed

+966
-48
lines changed

pkg/front_end/lib/src/fasta/source/diet_listener.dart

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ import '../type_inference/type_inference_engine.dart'
5050
show InferenceDataForTesting, TypeInferenceEngine;
5151
import '../type_inference/type_inferrer.dart' show TypeInferrer;
5252
import 'diet_parser.dart';
53+
import 'source_constructor_builder.dart';
54+
import 'source_enum_builder.dart';
5355
import 'source_field_builder.dart';
5456
import 'source_function_builder.dart';
5557
import 'source_library_builder.dart' show SourceLibraryBuilder;
@@ -962,6 +964,19 @@ class DietListener extends StackListenerImpl {
962964
void endEnum(Token enumKeyword, Token leftBrace, int memberCount) {
963965
debugEvent("Enum");
964966
checkEmpty(enumKeyword.charOffset);
967+
968+
SourceEnumBuilder? enumBuilder = currentClass as SourceEnumBuilder?;
969+
if (enumBuilder != null) {
970+
DeclaredSourceConstructorBuilder? defaultConstructorBuilder =
971+
enumBuilder.synthesizedDefaultConstructorBuilder;
972+
if (defaultConstructorBuilder != null) {
973+
BodyBuilder bodyBuilder =
974+
createFunctionListener(defaultConstructorBuilder);
975+
bodyBuilder.finishConstructor(
976+
defaultConstructorBuilder, AsyncMarker.Sync, new EmptyStatement());
977+
}
978+
}
979+
965980
currentDeclaration = null;
966981
memberScope = libraryBuilder.scope;
967982
}

pkg/front_end/lib/src/fasta/source/outline_builder.dart

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2329,7 +2329,7 @@ class OutlineBuilder extends StackListenerImpl {
23292329

23302330
// We pop more values than needed to reach typeVariables, offset and name.
23312331
List<TypeBuilder>? interfaces = pop() as List<TypeBuilder>?;
2332-
List<TypeBuilder>? mixins = pop() as List<TypeBuilder>?;
2332+
Object? mixins = pop();
23332333
List<TypeVariableBuilder>? typeVariables =
23342334
pop() as List<TypeVariableBuilder>?;
23352335
int charOffset = popCharOffset(); // identifier char offset.
@@ -2341,7 +2341,7 @@ class OutlineBuilder extends StackListenerImpl {
23412341
push(name ?? NullValue.Name);
23422342
push(charOffset);
23432343
push(typeVariables ?? NullValue.TypeVariables);
2344-
push(mixins ?? NullValue.TypeBuilderList);
2344+
push(mixins ?? NullValue.TypeBuilder);
23452345
push(interfaces ?? NullValue.TypeBuilderList);
23462346

23472347
push(enumKeyword.charOffset); // start char offset.
@@ -2364,7 +2364,7 @@ class OutlineBuilder extends StackListenerImpl {
23642364
int endCharOffset = popCharOffset();
23652365
int startCharOffset = popCharOffset();
23662366
pop() as List<TypeBuilder>?; // interfaces.
2367-
pop() as List<TypeBuilder>?; // mixins.
2367+
MixinApplicationBuilder? mixinBuilder = pop() as MixinApplicationBuilder?;
23682368
List<TypeVariableBuilder>? typeVariables =
23692369
pop() as List<TypeVariableBuilder>?;
23702370
int charOffset = popCharOffset(); // identifier char offset.
@@ -2373,8 +2373,15 @@ class OutlineBuilder extends StackListenerImpl {
23732373
checkEmpty(startCharOffset);
23742374

23752375
if (name is! ParserRecovery) {
2376-
libraryBuilder.addEnum(metadata, name as String, typeVariables,
2377-
enumConstantInfos, startCharOffset, charOffset, endCharOffset);
2376+
libraryBuilder.addEnum(
2377+
metadata,
2378+
name as String,
2379+
typeVariables,
2380+
mixinBuilder,
2381+
enumConstantInfos,
2382+
startCharOffset,
2383+
charOffset,
2384+
endCharOffset);
23782385
} else {
23792386
libraryBuilder
23802387
.endNestedDeclaration(
@@ -3199,15 +3206,23 @@ class OutlineBuilder extends StackListenerImpl {
31993206
if (mixins is ParserRecovery) {
32003207
push(new ParserRecovery(withKeyword.charOffset));
32013208
} else {
3202-
// TODO(cstefantsova): Handle enum mixins here.
3203-
push(mixins);
3209+
NamedTypeBuilder enumType = new NamedTypeBuilder(
3210+
"_Enum",
3211+
const NullabilityBuilder.omitted(),
3212+
/* arguments = */ null,
3213+
/* fileUri = */ null,
3214+
/* charOffset = */ null,
3215+
instanceTypeVariableAccess:
3216+
InstanceTypeVariableAccessState.Unexpected);
3217+
push(libraryBuilder.addMixinApplication(
3218+
enumType, mixins as List<TypeBuilder>, withKeyword.charOffset));
32043219
}
32053220
}
32063221

32073222
@override
32083223
void handleEnumNoWithClause() {
32093224
debugEvent("EnumNoWithClause");
3210-
push(NullValue.TypeBuilderList);
3225+
push(NullValue.TypeBuilder);
32113226
}
32123227

32133228
@override

pkg/front_end/lib/src/fasta/source/source_class_builder.dart

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ class SourceClassBuilder extends ClassBuilderImpl
105105

106106
SourceClassBuilder? _patchBuilder;
107107

108+
final bool isEnumMixin;
109+
108110
SourceClassBuilder(
109111
List<MetadataBuilder>? metadata,
110112
int modifiers,
@@ -124,7 +126,8 @@ class SourceClassBuilder extends ClassBuilderImpl
124126
{Class? cls,
125127
this.mixedInTypeBuilder,
126128
this.isMixinDeclaration = false,
127-
this.isMacro: false})
129+
this.isMacro: false,
130+
this.isEnumMixin: false})
128131
: actualCls = initializeClass(cls, typeVariables, name, parent,
129132
startCharOffset, nameOffset, charEndOffset, referencesFromIndexed),
130133
super(metadata, modifiers, name, typeVariables, supertype, interfaces,
@@ -195,6 +198,11 @@ class SourceClassBuilder extends ClassBuilderImpl
195198
if (supertypeBuilder != null) {
196199
supertypeBuilder = _checkSupertype(supertypeBuilder!);
197200
}
201+
if (isEnumMixin) {
202+
assert(supertypeBuilder?.name == "_Enum");
203+
supertypeBuilder?.resolveIn(coreLibrary.scope,
204+
supertypeBuilder?.charOffset ?? charOffset, fileUri, library);
205+
}
198206
Supertype? supertype =
199207
supertypeBuilder?.buildSupertype(library, charOffset, fileUri);
200208
if (supertype != null) {

pkg/front_end/lib/src/fasta/source/source_enum_builder.dart

Lines changed: 56 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import '../builder/named_type_builder.dart';
4646
import '../builder/nullability_builder.dart';
4747
import '../builder/procedure_builder.dart';
4848
import '../builder/type_builder.dart';
49+
import '../builder/type_declaration_builder.dart';
4950
import '../builder/type_variable_builder.dart';
5051
import '../fasta_codes.dart'
5152
show
@@ -86,7 +87,7 @@ class SourceEnumBuilder extends SourceClassBuilder {
8687

8788
final NamedTypeBuilder listType;
8889

89-
DeclaredSourceConstructorBuilder? _synthesizedDefaultConstructorBuilder;
90+
DeclaredSourceConstructorBuilder? synthesizedDefaultConstructorBuilder;
9091

9192
SourceEnumBuilder.internal(
9293
List<MetadataBuilder>? metadata,
@@ -99,7 +100,7 @@ class SourceEnumBuilder extends SourceClassBuilder {
99100
this.intType,
100101
this.listType,
101102
this.objectType,
102-
TypeBuilder enumType,
103+
TypeBuilder supertypeBuilder,
103104
this.stringType,
104105
SourceLibraryBuilder parent,
105106
int startCharOffset,
@@ -111,7 +112,7 @@ class SourceEnumBuilder extends SourceClassBuilder {
111112
0,
112113
name,
113114
typeVariables,
114-
enumType,
115+
supertypeBuilder,
115116
/* interfaces = */ null,
116117
/* onTypes = */ null,
117118
scope,
@@ -128,6 +129,7 @@ class SourceEnumBuilder extends SourceClassBuilder {
128129
List<MetadataBuilder>? metadata,
129130
String name,
130131
List<TypeVariableBuilder>? typeVariables,
132+
TypeBuilder? supertypeBuilder,
131133
List<EnumConstantInfo?>? enumConstantInfos,
132134
SourceLibraryBuilder parent,
133135
int startCharOffset,
@@ -173,7 +175,7 @@ class SourceEnumBuilder extends SourceClassBuilder {
173175
/* fileUri = */ null,
174176
/* charOffset = */ null,
175177
instanceTypeVariableAccess: InstanceTypeVariableAccessState.Unexpected);
176-
NamedTypeBuilder enumType = new NamedTypeBuilder(
178+
supertypeBuilder ??= new NamedTypeBuilder(
177179
"_Enum",
178180
const NullabilityBuilder.omitted(),
179181
/* arguments = */ null,
@@ -418,14 +420,14 @@ class SourceEnumBuilder extends SourceClassBuilder {
418420
intType,
419421
listType,
420422
objectType,
421-
enumType,
423+
supertypeBuilder,
422424
stringType,
423425
parent,
424426
startCharOffsetComputed,
425427
charOffset,
426428
charEndOffset,
427429
referencesFromIndexed)
428-
.._synthesizedDefaultConstructorBuilder =
430+
..synthesizedDefaultConstructorBuilder =
429431
synthesizedDefaultConstructorBuilder;
430432

431433
void setParent(String name, MemberBuilder? builder) {
@@ -458,9 +460,21 @@ class SourceEnumBuilder extends SourceClassBuilder {
458460
coreLibrary.scope, charOffset, fileUri, libraryBuilder);
459461
objectType.resolveIn(
460462
coreLibrary.scope, charOffset, fileUri, libraryBuilder);
461-
TypeBuilder supertypeBuilder = this.supertypeBuilder!;
462-
supertypeBuilder.resolveIn(
463-
coreLibrary.scope, charOffset, fileUri, libraryBuilder);
463+
TypeBuilder? supertypeBuilder = this.supertypeBuilder;
464+
NamedTypeBuilder? enumType;
465+
466+
while (enumType == null && supertypeBuilder is NamedTypeBuilder) {
467+
TypeDeclarationBuilder? superclassBuilder = supertypeBuilder.declaration;
468+
if (superclassBuilder is ClassBuilder &&
469+
superclassBuilder.isMixinApplication) {
470+
supertypeBuilder = superclassBuilder.supertypeBuilder;
471+
} else {
472+
enumType = supertypeBuilder;
473+
}
474+
}
475+
assert(enumType is NamedTypeBuilder && enumType.name == "_Enum");
476+
enumType!.resolveIn(coreLibrary.scope, charOffset, fileUri, libraryBuilder);
477+
464478
listType.resolveIn(coreLibrary.scope, charOffset, fileUri, libraryBuilder);
465479

466480
List<Expression> values = <Expression>[];
@@ -481,34 +495,39 @@ class SourceEnumBuilder extends SourceClassBuilder {
481495
valuesBuilder.build(libraryBuilder);
482496

483497
// The super initializer for the synthesized default constructor is
484-
// inserted here. Other constructors are handled in
485-
// [BodyBuilder.finishConstructor], as they are processed via the pipeline
486-
// for constructor parsing and building.
487-
if (_synthesizedDefaultConstructorBuilder != null) {
488-
Constructor constructor =
489-
_synthesizedDefaultConstructorBuilder!.build(libraryBuilder);
490-
ClassBuilder objectClass = objectType.declaration as ClassBuilder;
491-
ClassBuilder enumClass = supertypeBuilder.declaration as ClassBuilder;
492-
MemberBuilder? superConstructor = enumClass.findConstructorOrFactory(
493-
"", charOffset, fileUri, libraryBuilder);
494-
if (superConstructor == null || !superConstructor.isConstructor) {
495-
// TODO(ahe): Ideally, we would also want to check that [Object]'s
496-
// unnamed constructor requires no arguments. But that information
497-
// isn't always available at this point, and it's not really a
498-
// situation that can happen unless you start modifying the SDK
499-
// sources. (We should add a correct message. We no longer depend on
500-
// Object here.)
501-
library.addProblem(
502-
messageNoUnnamedConstructorInObject,
503-
objectClass.charOffset,
504-
objectClass.name.length,
505-
objectClass.fileUri);
506-
} else {
507-
constructor.initializers.add(new SuperInitializer(
508-
superConstructor.member as Constructor,
509-
new Arguments.forwarded(
510-
constructor.function, libraryBuilder.library))
511-
..parent = constructor);
498+
// inserted here if the enum's supertype is _Enum to preserve the legacy
499+
// behavior or having the old-style enum constants built in the outlines.
500+
// Other constructors are handled in [BodyBuilder.finishConstructor] as
501+
// they are processed via the pipeline for constructor parsing and
502+
// building.
503+
if (identical(this.supertypeBuilder, enumType)) {
504+
if (synthesizedDefaultConstructorBuilder != null) {
505+
Constructor constructor =
506+
synthesizedDefaultConstructorBuilder!.build(libraryBuilder);
507+
ClassBuilder objectClass = objectType.declaration as ClassBuilder;
508+
ClassBuilder enumClass = enumType.declaration as ClassBuilder;
509+
MemberBuilder? superConstructor = enumClass.findConstructorOrFactory(
510+
"", charOffset, fileUri, libraryBuilder);
511+
if (superConstructor == null || !superConstructor.isConstructor) {
512+
// TODO(ahe): Ideally, we would also want to check that [Object]'s
513+
// unnamed constructor requires no arguments. But that information
514+
// isn't always available at this point, and it's not really a
515+
// situation that can happen unless you start modifying the SDK
516+
// sources. (We should add a correct message. We no longer depend on
517+
// Object here.)
518+
library.addProblem(
519+
messageNoUnnamedConstructorInObject,
520+
objectClass.charOffset,
521+
objectClass.name.length,
522+
objectClass.fileUri);
523+
} else {
524+
constructor.initializers.add(new SuperInitializer(
525+
superConstructor.member as Constructor,
526+
new Arguments.forwarded(
527+
constructor.function, libraryBuilder.library))
528+
..parent = constructor);
529+
}
530+
synthesizedDefaultConstructorBuilder = null;
512531
}
513532
}
514533

pkg/front_end/lib/src/fasta/source/source_library_builder.dart

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2103,7 +2103,8 @@ class SourceLibraryBuilder extends LibraryBuilderImpl {
21032103
List<TypeVariableBuilder>? typeVariables,
21042104
int modifiers: 0,
21052105
List<TypeBuilder>? interfaces,
2106-
required bool isMacro}) {
2106+
required bool isMacro,
2107+
bool isEnumMixin: false}) {
21072108
if (name == null) {
21082109
// The following parameters should only be used when building a named
21092110
// mixin application.
@@ -2322,7 +2323,8 @@ class SourceLibraryBuilder extends LibraryBuilderImpl {
23222323
charEndOffset,
23232324
referencesFromIndexedClass,
23242325
mixedInTypeBuilder: isMixinDeclaration ? null : mixin,
2325-
isMacro: isNamedMixinApplication && isMacro);
2326+
isMacro: isNamedMixinApplication && isMacro,
2327+
isEnumMixin: isEnumMixin && i == 0);
23262328
// TODO(ahe, kmillikin): Should always be true?
23272329
// pkg/analyzer/test/src/summary/resynthesize_kernel_test.dart can't
23282330
// handle that :(
@@ -2791,6 +2793,7 @@ class SourceLibraryBuilder extends LibraryBuilderImpl {
27912793
List<MetadataBuilder>? metadata,
27922794
String name,
27932795
List<TypeVariableBuilder>? typeVariables,
2796+
TypeBuilder? supertypeBuilder,
27942797
List<EnumConstantInfo?>? enumConstantInfos,
27952798
int startCharOffset,
27962799
int charOffset,
@@ -2812,6 +2815,9 @@ class SourceLibraryBuilder extends LibraryBuilderImpl {
28122815
metadata,
28132816
name,
28142817
typeVariables,
2818+
applyMixins(supertypeBuilder, startCharOffset, charOffset,
2819+
charEndOffset, name, /* isMixinDeclaration = */ false,
2820+
typeVariables: typeVariables, isMacro: false, isEnumMixin: true),
28152821
enumConstantInfos,
28162822
this,
28172823
startCharOffset,

pkg/front_end/test/spell_checking_list_common.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1029,6 +1029,7 @@ entity's
10291029
entries
10301030
entry
10311031
enum
1032+
enum's
10321033
enumerated
10331034
enumeration
10341035
enumerations
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Copyright (c) 2022, 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+
class A {
6+
String get foo => "foo";
7+
}
8+
9+
class B {
10+
int bar() => 42;
11+
}
12+
13+
mixin M {
14+
void set callOnAssignment(void Function() f) {
15+
f();
16+
}
17+
}
18+
19+
enum E1 with A { one, two }
20+
21+
enum E2 with A, B { one, two }
22+
23+
enum E3 with M { one, two }
24+
25+
expectEquals(x, y) {
26+
if (x != y) {
27+
throw "Expected '$x' and '$y' to be equal.";
28+
}
29+
}
30+
31+
expectThrows(void Function() f) {
32+
try {
33+
f();
34+
throw "Expected function to throw.";
35+
} catch (e) {}
36+
}
37+
38+
void throwOnCall() {
39+
throw 42;
40+
}
41+
42+
main() {
43+
expectEquals(E1.one.foo, "foo");
44+
expectEquals(E1.two.foo, "foo");
45+
expectEquals(E2.one.foo, "foo");
46+
expectEquals(E2.two.foo, "foo");
47+
expectEquals(E2.one.bar(), "bar");
48+
expectEquals(E2.two.bar(), "bar");
49+
expectThrows(E3.one.callOnAssignment = throwOnCall);
50+
expectThrows(E3.two.callOnAssignment = throwOnCall);
51+
}

0 commit comments

Comments
 (0)