Inheritance inference for methods in augmentations, and using combined interfaces for classes and mixins.

Change-Id: I27af575219ab1e3bfba3f894ebc83e0806f7f968
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/314460
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Samuel Rawlins <srawlins@google.com>
This commit is contained in:
Konstantin Shcheglov 2023-07-18 18:51:27 +00:00 committed by Commit Queue
parent f06a1d58d8
commit 6b4337ab69
4 changed files with 508 additions and 40 deletions

View file

@ -132,6 +132,22 @@ extension InterfaceElementExtension on InterfaceElement {
}
}
extension MixinElementExtension on MixinElement {
/// The result of applying augmentations.
///
/// The target must be a declaration, not an augmentation.
/// This getter will throw, if this is not the case.
AugmentedMixinElement get augmentedOfDeclaration {
if (isAugmentation) {
throw StateError(
'The target must be a declaration, not an augmentation.',
);
}
// This is safe because declarations always have it.
return augmented!;
}
}
extension ParameterElementExtensions on ParameterElement {
/// Return [ParameterElement] with the specified properties replaced.
ParameterElement copyWith({

View file

@ -448,6 +448,7 @@ class InheritanceManager3 {
}
Interface _getInterfaceClass(InterfaceElement element) {
final augmented = element.augmentedOfDeclaration;
var classLibrary = element.library;
var isNonNullableByDefault = classLibrary.isNonNullableByDefault;
@ -485,7 +486,7 @@ class InheritanceManager3 {
// multiple candidates happen only when we merge super and multiple
// interfaces. Consider using `Map<Name, ExecutableElement>` here.
var mixinsConflicts = <List<Conflict>>[];
for (var mixin in element.mixins) {
for (var mixin in augmented.mixins) {
var mixinElement = mixin.element;
var substitution = Substitution.fromInterfaceType(mixin);
var mixinInterface = getInterface(mixinElement);
@ -573,7 +574,7 @@ class InheritanceManager3 {
superImplemented.add(implemented);
}
for (var interface in element.interfaces) {
for (var interface in augmented.interfaces) {
_addCandidates(
namedCandidates: namedCandidates,
substitution: Substitution.fromInterfaceType(interface),
@ -648,11 +649,12 @@ class InheritanceManager3 {
}
Interface _getInterfaceMixin(MixinElement element) {
final augmented = element.augmentedOfDeclaration;
var classLibrary = element.library;
var isNonNullableByDefault = classLibrary.isNonNullableByDefault;
var superCandidates = <Name, List<ExecutableElement>>{};
for (var constraint in element.superclassConstraints) {
for (var constraint in augmented.superclassConstraints) {
var substitution = Substitution.fromInterfaceType(constraint);
var interfaceObj = getInterface(constraint.element);
_addCandidates(
@ -674,7 +676,7 @@ class InheritanceManager3 {
);
var interfaceCandidates = Map.of(superCandidates);
for (var interface in element.interfaces) {
for (var interface in augmented.interfaces) {
_addCandidates(
namedCandidates: interfaceCandidates,
substitution: Substitution.fromInterfaceType(interface),

View file

@ -6,6 +6,7 @@ import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/element/extensions.dart';
import 'package:analyzer/src/dart/element/inheritance_manager3.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/dart/element/type_algebra.dart';
@ -31,8 +32,8 @@ class InstanceMemberInferrer {
/// Infer type information for all of the instance members in the given
/// compilation [unit].
void inferCompilationUnit(CompilationUnitElement unit) {
typeSystem = unit.library.typeSystem as TypeSystemImpl;
void inferCompilationUnit(CompilationUnitElementImpl unit) {
typeSystem = unit.library.typeSystem;
isNonNullableByDefault = typeSystem.isNonNullableByDefault;
_inferClasses(unit.classes);
_inferClasses(unit.enums);
@ -301,58 +302,69 @@ class InstanceMemberInferrer {
/// Infer type information for all of the instance members in the given
/// [classElement].
void _inferClass(InterfaceElement classElement) {
void _inferClass(InterfaceElementImpl classElement) {
if (classElement.isAugmentation) {
return;
}
if (classElement.hasBeenInferred) {
return;
}
_setInducedModifier(classElement);
if (classElement is InterfaceElementImpl) {
if (classElement.hasBeenInferred) {
return;
}
if (!elementsBeingInferred.add(classElement)) {
// We have found a circularity in the class hierarchy. For now we just
// stop trying to infer any type information for any classes that
// inherit from any class in the cycle. We could potentially limit the
// algorithm to only not inferring types in the classes in the cycle,
// but it isn't clear that the results would be significantly better.
throw _CycleException();
}
try {
//
// Ensure that all of instance members in the supertypes have had types
// inferred for them.
//
_inferType(classElement.supertype);
classElement.mixins.forEach(_inferType);
classElement.interfaces.forEach(_inferType);
//
// Then infer the types for the members.
//
currentInterfaceElement = classElement;
for (var field in classElement.fields) {
if (!elementsBeingInferred.add(classElement)) {
// We have found a circularity in the class hierarchy. For now we just
// stop trying to infer any type information for any classes that
// inherit from any class in the cycle. We could potentially limit the
// algorithm to only not inferring types in the classes in the cycle,
// but it isn't clear that the results would be significantly better.
throw _CycleException();
}
try {
//
// Ensure that all of instance members in the supertypes have had types
// inferred for them.
//
final augmented = classElement.augmentedOfDeclaration;
_inferType(classElement.supertype);
augmented.mixins.forEach(_inferType);
augmented.interfaces.forEach(_inferType);
//
// Then infer the types for the members.
//
// TODO(scheglov) get other members from the container
currentInterfaceElement = classElement;
for (final container in classElement.withAugmentations) {
for (final field in classElement.fields) {
_inferAccessorOrField(
field: field,
);
}
for (var accessor in classElement.accessors) {
for (final accessor in classElement.accessors) {
_inferAccessorOrField(
accessor: accessor,
);
}
for (var method in classElement.methods) {
for (final method in container.methods) {
_inferExecutable(method);
}
//
// Infer initializing formal parameter types. This must happen after
// field types are inferred.
//
classElement.constructors.forEach(_inferConstructor);
classElement.hasBeenInferred = true;
} finally {
elementsBeingInferred.remove(classElement);
for (final constructor in classElement.constructors) {
_inferConstructor(constructor);
}
}
classElement.hasBeenInferred = true;
} finally {
elementsBeingInferred.remove(classElement);
}
}
void _inferClasses(List<InterfaceElement> elements) {
void _inferClasses(List<InterfaceElementImpl> elements) {
for (final element in elements) {
try {
_inferClass(element);
@ -551,7 +563,8 @@ class InstanceMemberInferrer {
/// interface [type].
void _inferType(InterfaceType? type) {
if (type != null) {
_inferClass(type.element);
final element = type.element as InterfaceElementImpl;
_inferClass(element);
}
}

View file

@ -784,6 +784,223 @@ library
''');
}
test_inferTypes_method_ofAugment() async {
newFile('$testPackageLibPath/a.dart', r'''
class A {
int foo(String a) => 0;
}
''');
newFile('$testPackageLibPath/b.dart', r'''
library augment 'test.dart';
augment class B {
foo(a) => 0;
}
''');
var library = await buildLibrary(r'''
import 'a.dart';
import augment 'b.dart';
class B extends A {}
''');
checkElementText(library, r'''
library
imports
package:test/a.dart
definingUnit
classes
class B @49
augmentation: self::@augmentation::package:test/b.dart::@class::B
supertype: A
constructors
synthetic @-1
superConstructor: package:test/a.dart::@class::A::@constructor::new
augmented
methods
self::@augmentation::package:test/b.dart::@class::B::@method::foo
augmentationImports
package:test/b.dart
definingUnit
classes
augment class B @43
augmentationTarget: self::@class::B
methods
foo @49
parameters
requiredPositional a @53
type: String
returnType: int
''');
}
test_inferTypes_method_usingAugmentation_interface() async {
newFile('$testPackageLibPath/a.dart', r'''
class A {
int foo(String a) => 0;
}
''');
newFile('$testPackageLibPath/b.dart', r'''
library augment 'test.dart';
import 'a.dart';
augment class B implements A {}
''');
var library = await buildLibrary(r'''
import augment 'b.dart';
class B {
foo(a) => 0;
}
''');
checkElementText(library, r'''
library
definingUnit
classes
class B @32
augmentation: self::@augmentation::package:test/b.dart::@class::B
constructors
synthetic @-1
methods
foo @38
parameters
requiredPositional a @42
type: String
returnType: int
augmented
interfaces
A
methods
self::@class::B::@method::foo
augmentationImports
package:test/b.dart
imports
package:test/a.dart
definingUnit
classes
augment class B @60
augmentationTarget: self::@class::B
interfaces
A
''');
}
test_inferTypes_method_usingAugmentation_mixin() async {
newFile('$testPackageLibPath/a.dart', r'''
mixin A {
int foo(String a) => 0;
}
''');
newFile('$testPackageLibPath/b.dart', r'''
library augment 'test.dart';
import 'a.dart';
augment class B with A {}
''');
var library = await buildLibrary(r'''
import augment 'b.dart';
class B {
foo(a) => 0;
}
''');
checkElementText(library, r'''
library
definingUnit
classes
class B @32
augmentation: self::@augmentation::package:test/b.dart::@class::B
constructors
synthetic @-1
methods
foo @38
parameters
requiredPositional a @42
type: String
returnType: int
augmented
mixins
A
methods
self::@class::B::@method::foo
augmentationImports
package:test/b.dart
imports
package:test/a.dart
definingUnit
classes
augment class B @60
augmentationTarget: self::@class::B
mixins
A
''');
}
test_inferTypes_method_withAugment() async {
newFile('$testPackageLibPath/a.dart', r'''
class A {
int foo(String a) => 0;
}
''');
newFile('$testPackageLibPath/b.dart', r'''
library augment 'test.dart';
augment class B {
foo(a) => 0;
}
''');
var library = await buildLibrary(r'''
import 'a.dart';
import augment 'b.dart';
class B extends A {
foo(a) => 0;
}
''');
checkElementText(library, r'''
library
imports
package:test/a.dart
definingUnit
classes
class B @49
augmentation: self::@augmentation::package:test/b.dart::@class::B
supertype: A
constructors
synthetic @-1
superConstructor: package:test/a.dart::@class::A::@constructor::new
methods
foo @65
parameters
requiredPositional a @69
type: String
returnType: int
augmentation: self::@augmentation::package:test/b.dart::@class::B::@method::foo
augmented
methods
self::@augmentation::package:test/b.dart::@class::B::@method::foo
augmentationImports
package:test/b.dart
definingUnit
classes
augment class B @43
augmentationTarget: self::@class::B
methods
foo @49
parameters
requiredPositional a @53
type: String
returnType: int
''');
}
test_modifiers_abstract() async {
newFile('$testPackageLibPath/a.dart', r'''
library augment 'test.dart';
@ -46314,6 +46531,226 @@ library
''');
}
test_inferTypes_method_ofAugment() async {
newFile('$testPackageLibPath/a.dart', r'''
class A {
int foo(String a) => 0;
}
''');
newFile('$testPackageLibPath/b.dart', r'''
library augment 'test.dart';
augment mixin B {
foo(a) => 0;
}
''');
var library = await buildLibrary(r'''
import 'a.dart';
import augment 'b.dart';
mixin B on A {}
''');
checkElementText(library, r'''
library
imports
package:test/a.dart
definingUnit
mixins
mixin B @49
augmentation: self::@augmentation::package:test/b.dart::@mixin::B
superclassConstraints
A
augmented
superclassConstraints
A
methods
self::@augmentation::package:test/b.dart::@mixin::B::@method::foo
augmentationImports
package:test/b.dart
definingUnit
mixins
augment mixin B @43
augmentationTarget: self::@mixin::B
methods
foo @49
parameters
requiredPositional a @53
type: String
returnType: int
''');
}
test_inferTypes_method_usingAugmentation_interface() async {
newFile('$testPackageLibPath/a.dart', r'''
class A {
int foo(String a) => 0;
}
''');
newFile('$testPackageLibPath/b.dart', r'''
library augment 'test.dart';
import 'a.dart';
augment mixin B implements A {}
''');
var library = await buildLibrary(r'''
import augment 'b.dart';
mixin B {
foo(a) => 0;
}
''');
checkElementText(library, r'''
library
definingUnit
mixins
mixin B @32
augmentation: self::@augmentation::package:test/b.dart::@mixin::B
superclassConstraints
Object
methods
foo @38
parameters
requiredPositional a @42
type: String
returnType: int
augmented
superclassConstraints
Object
interfaces
A
methods
self::@mixin::B::@method::foo
augmentationImports
package:test/b.dart
imports
package:test/a.dart
definingUnit
mixins
augment mixin B @60
augmentationTarget: self::@mixin::B
interfaces
A
''');
}
test_inferTypes_method_usingAugmentation_superclassConstraint() async {
newFile('$testPackageLibPath/a.dart', r'''
class A {
int foo(String a) => 0;
}
''');
newFile('$testPackageLibPath/b.dart', r'''
library augment 'test.dart';
import 'a.dart';
augment mixin B on A {}
''');
var library = await buildLibrary(r'''
import augment 'b.dart';
mixin B {
foo(a) => 0;
}
''');
checkElementText(library, r'''
library
definingUnit
mixins
mixin B @32
augmentation: self::@augmentation::package:test/b.dart::@mixin::B
superclassConstraints
Object
methods
foo @38
parameters
requiredPositional a @42
type: String
returnType: int
augmented
superclassConstraints
Object
A
methods
self::@mixin::B::@method::foo
augmentationImports
package:test/b.dart
imports
package:test/a.dart
definingUnit
mixins
augment mixin B @60
augmentationTarget: self::@mixin::B
superclassConstraints
A
''');
}
test_inferTypes_method_withAugment() async {
newFile('$testPackageLibPath/a.dart', r'''
class A {
int foo(String a) => 0;
}
''');
newFile('$testPackageLibPath/b.dart', r'''
library augment 'test.dart';
augment mixin B {
foo(a) => 0;
}
''');
var library = await buildLibrary(r'''
import 'a.dart';
import augment 'b.dart';
mixin B on A {
foo(a) => 0;
}
''');
checkElementText(library, r'''
library
imports
package:test/a.dart
definingUnit
mixins
mixin B @49
augmentation: self::@augmentation::package:test/b.dart::@mixin::B
superclassConstraints
A
methods
foo @60
parameters
requiredPositional a @64
type: String
returnType: int
augmentation: self::@augmentation::package:test/b.dart::@mixin::B::@method::foo
augmented
superclassConstraints
A
methods
self::@augmentation::package:test/b.dart::@mixin::B::@method::foo
augmentationImports
package:test/b.dart
definingUnit
mixins
augment mixin B @43
augmentationTarget: self::@mixin::B
methods
foo @49
parameters
requiredPositional a @53
type: String
returnType: int
''');
}
test_modifiers_base() async {
newFile('$testPackageLibPath/a.dart', r'''
library augment 'test.dart';