Skip to content

Commit ea21df9

Browse files
kallentuCommit Queue
authored andcommitted
[cfe] Disallow implementing a legacy library subclass of a final/base class in the core libraries.
This behaviour should only happen when a post-feature library implements a pre-feature library declaration that has a final/base core library class as a super declaration. Bug: #52115 Change-Id: If42129ba3ba7e337cc6ffc21604c6d0f2976344c Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/301503 Reviewed-by: Nate Bosch <[email protected]> Commit-Queue: Kallen Tu <[email protected]> Reviewed-by: Johnni Winther <[email protected]>
1 parent 3fb1305 commit ea21df9

29 files changed

+699
-190
lines changed

pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart

Lines changed: 29 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -367,34 +367,6 @@ Message _withArgumentsBaseClassImplementedOutsideOfLibrary(String name) {
367367
arguments: {'name': name});
368368
}
369369

370-
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
371-
const Template<Message Function(String name, String name2)>
372-
templateBaseClassImplementedOutsideOfLibraryCause =
373-
const Template<Message Function(String name, String name2)>(
374-
problemMessageTemplate:
375-
r"""The type '#name' is a subtype of '#name2', and '#name2' is defined here.""",
376-
withArguments: _withArgumentsBaseClassImplementedOutsideOfLibraryCause);
377-
378-
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
379-
const Code<Message Function(String name, String name2)>
380-
codeBaseClassImplementedOutsideOfLibraryCause =
381-
const Code<Message Function(String name, String name2)>(
382-
"BaseClassImplementedOutsideOfLibraryCause",
383-
severity: Severity.context);
384-
385-
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
386-
Message _withArgumentsBaseClassImplementedOutsideOfLibraryCause(
387-
String name, String name2) {
388-
if (name.isEmpty) throw 'No name provided';
389-
name = demangleMixinApplicationName(name);
390-
if (name2.isEmpty) throw 'No name provided';
391-
name2 = demangleMixinApplicationName(name2);
392-
return new Message(codeBaseClassImplementedOutsideOfLibraryCause,
393-
problemMessage:
394-
"""The type '${name}' is a subtype of '${name2}', and '${name2}' is defined here.""",
395-
arguments: {'name': name, 'name2': name2});
396-
}
397-
398370
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
399371
const Code<Null> codeBaseEnum = messageBaseEnum;
400372

@@ -431,6 +403,35 @@ Message _withArgumentsBaseMixinImplementedOutsideOfLibrary(String name) {
431403
arguments: {'name': name});
432404
}
433405

406+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
407+
const Template<Message Function(String name, String name2)>
408+
templateBaseOrFinalClassImplementedOutsideOfLibraryCause =
409+
const Template<Message Function(String name, String name2)>(
410+
problemMessageTemplate:
411+
r"""The type '#name' is a subtype of '#name2', and '#name2' is defined here.""",
412+
withArguments:
413+
_withArgumentsBaseOrFinalClassImplementedOutsideOfLibraryCause);
414+
415+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
416+
const Code<Message Function(String name, String name2)>
417+
codeBaseOrFinalClassImplementedOutsideOfLibraryCause =
418+
const Code<Message Function(String name, String name2)>(
419+
"BaseOrFinalClassImplementedOutsideOfLibraryCause",
420+
severity: Severity.context);
421+
422+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
423+
Message _withArgumentsBaseOrFinalClassImplementedOutsideOfLibraryCause(
424+
String name, String name2) {
425+
if (name.isEmpty) throw 'No name provided';
426+
name = demangleMixinApplicationName(name);
427+
if (name2.isEmpty) throw 'No name provided';
428+
name2 = demangleMixinApplicationName(name2);
429+
return new Message(codeBaseOrFinalClassImplementedOutsideOfLibraryCause,
430+
problemMessage:
431+
"""The type '${name}' is a subtype of '${name2}', and '${name2}' is defined here.""",
432+
arguments: {'name': name, 'name2': name2});
433+
}
434+
434435
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
435436
const Template<
436437
Message Function(

pkg/_fe_analyzer_shared/test/inheritance/data/function.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ class B
1616
/*cfe|cfe:builder.class: C:C,Object,_C&Object&Function*/
1717
/*analyzer.class: C:C,Object*/
1818
/*cfe|cfe:builder.class: _C&Object&Function:Object,_C&Object&Function*/
19-
class C extends Object
19+
class /*cfe|cfe:builder.error: SubtypeOfFinalIsNotBaseFinalOrSealed*/ C
20+
extends Object
2021
with /*analyzer.error: CompileTimeErrorCode.CLASS_USED_AS_MIXIN*/
2122
/*cfe|cfe:builder.error: CantUseClassAsMixin*/ Function {}
2223

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

Lines changed: 74 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -2301,13 +2301,38 @@ severity: $severity
23012301
if (!superclass.isBase &&
23022302
!superclass.isFinal &&
23032303
!superclass.isSealed &&
2304-
!superclass.cls.isAnonymousMixin) {
2304+
!superclass.cls.isAnonymousMixin &&
2305+
superclass.libraryBuilder.library.languageVersion >=
2306+
ExperimentalFlag.classModifiers.experimentEnabledVersion) {
23052307
// Only report an error on the nearest subtype that does not fulfill
23062308
// the base or final subtype restriction.
23072309
return;
23082310
}
23092311

23102312
if (baseOrFinalSuperClass.isFinal) {
2313+
// Don't check base and final subtyping restriction if the supertype
2314+
// is a final type used outside of its library.
2315+
if (cls.libraryBuilder.origin !=
2316+
baseOrFinalSuperClass.libraryBuilder.origin) {
2317+
// In the special case where the 'baseOrFinalSuperClass' is a core
2318+
// library class and we are indirectly subtyping from a superclass
2319+
// that's from a pre-feature library, we want to produce a final
2320+
// transitivity error.
2321+
//
2322+
// For implements clauses with the above scenario, we avoid
2323+
// over-reporting since there will already be a
2324+
// [FinalClassImplementedOutsideOfLibrary] error.
2325+
//
2326+
// TODO(kallentu): Avoid over-reporting for with clauses.
2327+
if (baseOrFinalSuperClass.libraryBuilder.origin ==
2328+
superclass.libraryBuilder.origin ||
2329+
!baseOrFinalSuperClass.libraryBuilder.importUri
2330+
.isScheme("dart") ||
2331+
implementsBuilder != null) {
2332+
return;
2333+
}
2334+
}
2335+
23112336
cls.addProblem(
23122337
templateSubtypeOfFinalIsNotBaseFinalOrSealed.withArguments(
23132338
cls.fullNameForErrors,
@@ -2330,15 +2355,9 @@ severity: $severity
23302355
final TypeDeclarationBuilder? supertypeDeclaration =
23312356
unaliasDeclaration(supertypeBuilder);
23322357
if (supertypeDeclaration is ClassBuilder) {
2333-
if (isClassModifiersEnabled(supertypeDeclaration)) {
2334-
if (cls.libraryBuilder.origin ==
2335-
supertypeDeclaration.libraryBuilder.origin ||
2336-
!supertypeDeclaration.isFinal) {
2337-
// Don't check base and final subtyping restriction if the supertype
2338-
// is a final type used outside of its library.
2339-
checkForBaseFinalRestriction(supertypeDeclaration);
2340-
}
2358+
checkForBaseFinalRestriction(supertypeDeclaration);
23412359

2360+
if (isClassModifiersEnabled(supertypeDeclaration)) {
23422361
if (cls.libraryBuilder.origin !=
23432362
supertypeDeclaration.libraryBuilder.origin &&
23442363
!mayIgnoreClassModifiers(supertypeDeclaration)) {
@@ -2385,15 +2404,9 @@ severity: $severity
23852404
final TypeDeclarationBuilder? mixedInTypeDeclaration =
23862405
unaliasDeclaration(mixedInTypeBuilder);
23872406
if (mixedInTypeDeclaration is ClassBuilder) {
2388-
if (isClassModifiersEnabled(mixedInTypeDeclaration)) {
2389-
if (cls.libraryBuilder.origin ==
2390-
mixedInTypeDeclaration.libraryBuilder.origin ||
2391-
!mixedInTypeDeclaration.isFinal) {
2392-
// Don't check base and final subtyping restriction if the supertype
2393-
// is a final type used outside of its library.
2394-
checkForBaseFinalRestriction(mixedInTypeDeclaration);
2395-
}
2407+
checkForBaseFinalRestriction(mixedInTypeDeclaration);
23962408

2409+
if (isClassModifiersEnabled(mixedInTypeDeclaration)) {
23972410
// Check for classes being used as mixins. Only classes declared with
23982411
// a 'mixin' modifier are allowed to be mixed in.
23992412
if (cls.isMixinApplication &&
@@ -2428,79 +2441,55 @@ severity: $severity
24282441
final TypeDeclarationBuilder? interfaceDeclaration =
24292442
unaliasDeclaration(interfaceBuilder);
24302443
if (interfaceDeclaration is ClassBuilder) {
2431-
if (isClassModifiersEnabled(interfaceDeclaration)) {
2432-
if (cls.libraryBuilder.origin ==
2433-
interfaceDeclaration.libraryBuilder.origin ||
2434-
!interfaceDeclaration.isFinal) {
2435-
// Don't check base and final subtyping restriction if the
2436-
// supertype is a final type used outside of its library.
2437-
checkForBaseFinalRestriction(interfaceDeclaration,
2438-
implementsBuilder: interfaceBuilder);
2439-
}
2444+
checkForBaseFinalRestriction(interfaceDeclaration,
2445+
implementsBuilder: interfaceBuilder);
2446+
2447+
ClassBuilder? checkedClass = interfaceDeclaration;
2448+
while (checkedClass != null) {
2449+
if (cls.libraryBuilder.origin !=
2450+
checkedClass.libraryBuilder.origin &&
2451+
!mayIgnoreClassModifiers(checkedClass)) {
2452+
final List<LocatedMessage> context = [
2453+
if (checkedClass != interfaceDeclaration)
2454+
templateBaseOrFinalClassImplementedOutsideOfLibraryCause
2455+
.withArguments(interfaceDeclaration.fullNameForErrors,
2456+
checkedClass.fullNameForErrors)
2457+
.withLocation(checkedClass.fileUri,
2458+
checkedClass.charOffset, noLength)
2459+
];
24402460

2441-
ClassBuilder? checkedClass = interfaceDeclaration;
2442-
while (checkedClass != null) {
2443-
if (cls.libraryBuilder.origin !=
2444-
checkedClass.libraryBuilder.origin &&
2445-
!mayIgnoreClassModifiers(checkedClass)) {
2461+
if (checkedClass.isBase && !cls.cls.isAnonymousMixin) {
24462462
// Report an error for a class implementing a base class outside
24472463
// of its library.
2448-
if (checkedClass.isBase && !cls.cls.isAnonymousMixin) {
2449-
if (checkedClass.isMixinDeclaration) {
2450-
cls.addProblem(
2451-
templateBaseMixinImplementedOutsideOfLibrary
2452-
.withArguments(checkedClass.fullNameForErrors),
2453-
interfaceBuilder.charOffset ?? TreeNode.noOffset,
2454-
noLength,
2455-
context: [
2456-
if (checkedClass != interfaceDeclaration)
2457-
templateBaseClassImplementedOutsideOfLibraryCause
2458-
.withArguments(
2459-
interfaceDeclaration.fullNameForErrors,
2460-
checkedClass.fullNameForErrors)
2461-
.withLocation(checkedClass.fileUri,
2462-
checkedClass.charOffset, noLength)
2463-
]);
2464-
} else {
2465-
cls.addProblem(
2466-
templateBaseClassImplementedOutsideOfLibrary
2467-
.withArguments(checkedClass.fullNameForErrors),
2468-
interfaceBuilder.charOffset ?? TreeNode.noOffset,
2469-
noLength,
2470-
context: [
2471-
if (checkedClass != interfaceDeclaration)
2472-
templateBaseClassImplementedOutsideOfLibraryCause
2473-
.withArguments(
2474-
interfaceDeclaration.fullNameForErrors,
2475-
checkedClass.fullNameForErrors)
2476-
.withLocation(checkedClass.fileUri,
2477-
checkedClass.charOffset, noLength)
2478-
]);
2479-
}
2480-
// Break to only report one error.
2481-
break;
2482-
} else if (checkedClass.isFinal &&
2483-
checkedClass == interfaceDeclaration) {
2484-
if (cls.cls.isAnonymousMixin) {
2485-
cls.addProblem(
2486-
templateFinalClassUsedAsMixinConstraintOutsideOfLibrary
2487-
.withArguments(
2488-
interfaceDeclaration.fullNameForErrors),
2489-
interfaceBuilder.charOffset ?? TreeNode.noOffset,
2490-
noLength);
2491-
} else {
2492-
cls.addProblem(
2493-
templateFinalClassImplementedOutsideOfLibrary
2494-
.withArguments(
2495-
interfaceDeclaration.fullNameForErrors),
2496-
interfaceBuilder.charOffset ?? TreeNode.noOffset,
2497-
noLength);
2498-
}
2499-
break;
2500-
}
2464+
final Template<Message Function(String)> template =
2465+
checkedClass.isMixinDeclaration
2466+
? templateBaseMixinImplementedOutsideOfLibrary
2467+
: templateBaseClassImplementedOutsideOfLibrary;
2468+
cls.addProblem(
2469+
template.withArguments(checkedClass.fullNameForErrors),
2470+
interfaceBuilder.charOffset ?? TreeNode.noOffset,
2471+
noLength,
2472+
context: context);
2473+
// Break to only report one error.
2474+
break;
2475+
} else if (checkedClass.isFinal) {
2476+
// Report an error for a class implementing a final class
2477+
// outside of its library.
2478+
final Template<Message Function(String)> template = cls
2479+
.cls.isAnonymousMixin &&
2480+
checkedClass == interfaceDeclaration
2481+
? templateFinalClassUsedAsMixinConstraintOutsideOfLibrary
2482+
: templateFinalClassImplementedOutsideOfLibrary;
2483+
cls.addProblem(
2484+
template.withArguments(checkedClass.fullNameForErrors),
2485+
interfaceBuilder.charOffset ?? TreeNode.noOffset,
2486+
noLength,
2487+
context: context);
2488+
// Break to only report one error.
2489+
break;
25012490
}
2502-
checkedClass = classToBaseOrFinalSuperClass[checkedClass];
25032491
}
2492+
checkedClass = classToBaseOrFinalSuperClass[checkedClass];
25042493
}
25052494

25062495
// Report error for implementing a sealed class or a sealed mixin

pkg/front_end/messages.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6355,7 +6355,7 @@ BaseClassImplementedOutsideOfLibrary:
63556355
lib.dart:
63566356
base class A {}
63576357

6358-
BaseClassImplementedOutsideOfLibraryCause:
6358+
BaseOrFinalClassImplementedOutsideOfLibraryCause:
63596359
problemMessage: "The type '#name' is a subtype of '#name2', and '#name2' is defined here."
63606360
severity: CONTEXT
63616361

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
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+
import 'dart:collection';
6+
import "main_lib.dart";
7+
8+
// Implementing a legacy class that implements a core library base class.
9+
abstract base class LegacyImplementBase<E extends LinkedListEntry<E>>
10+
implements LegacyImplementBaseCore<E> {}
11+
12+
// Implementing a legacy class that implements a core library final class.
13+
final class LegacyImplementFinal implements LegacyImplementFinalCore {
14+
int get key => 0;
15+
int get value => 1;
16+
String toString() => "Bad";
17+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
library /*isNonNullableByDefault*/;
2+
//
3+
// Problems in library:
4+
//
5+
// pkg/front_end/testcases/class_modifiers/issue52115/main.dart:10:16: Error: The class 'LinkedList' can't be implemented outside of its library because it's a base class.
6+
// implements LegacyImplementBaseCore<E> {}
7+
// ^
8+
// sdk/lib/collection/linked_list.dart:81:12: Context: The type 'LegacyImplementBaseCore' is a subtype of 'LinkedList', and 'LinkedList' is defined here.
9+
// base class LinkedList<E extends LinkedListEntry<E>> extends Iterable<E> {
10+
// ^
11+
//
12+
// pkg/front_end/testcases/class_modifiers/issue52115/main.dart:13:45: Error: The class 'MapEntry' can't be implemented outside of its library because it's a final class.
13+
// final class LegacyImplementFinal implements LegacyImplementFinalCore {
14+
// ^
15+
// sdk/lib/core/map.dart:472:13: Context: The type 'LegacyImplementFinalCore' is a subtype of 'MapEntry', and 'MapEntry' is defined here.
16+
// final class MapEntry<K, V> {
17+
// ^
18+
//
19+
import self as self;
20+
import "dart:collection" as col;
21+
import "dart:core" as core;
22+
import "main_lib.dart" as mai;
23+
24+
import "dart:collection";
25+
import "org-dartlang-testcase:///main_lib.dart";
26+
27+
abstract base class LegacyImplementBase<E extends col::LinkedListEntry<self::LegacyImplementBase::E> = col::LinkedListEntry<dynamic>> extends core::Object implements mai::LegacyImplementBaseCore<self::LegacyImplementBase::E> {
28+
synthetic constructor •() → self::LegacyImplementBase<self::LegacyImplementBase::E>
29+
: super core::Object::•()
30+
;
31+
}
32+
final class LegacyImplementFinal extends core::Object implements mai::LegacyImplementFinalCore {
33+
synthetic constructor •() → self::LegacyImplementFinal
34+
: super core::Object::•()
35+
;
36+
get key() → core::int
37+
return 0;
38+
get value() → core::int
39+
return 1;
40+
method toString() → core::String
41+
return "Bad";
42+
}
43+
44+
library /*isNonNullableByDefault*/;
45+
import self as mai;
46+
import "dart:collection" as col;
47+
import "dart:core" as core;
48+
49+
import "dart:collection";
50+
51+
abstract class LegacyImplementBaseCore<E extends col::LinkedListEntry<mai::LegacyImplementBaseCore::E> = col::LinkedListEntry<dynamic>> extends core::Object implements col::LinkedList<mai::LegacyImplementBaseCore::E> {
52+
synthetic constructor •() → mai::LegacyImplementBaseCore<mai::LegacyImplementBaseCore::E>
53+
: super core::Object::•()
54+
;
55+
}
56+
class LegacyImplementFinalCore extends core::Object implements core::MapEntry<core::int, core::int> {
57+
synthetic constructor •() → mai::LegacyImplementFinalCore
58+
: super core::Object::•()
59+
;
60+
get key() → core::int
61+
return 0;
62+
get value() → core::int
63+
return 1;
64+
method toString() → core::String
65+
return "Bad";
66+
}

0 commit comments

Comments
 (0)