Correct implementation of expand-to-type-variable error check

Bug: #45351
Change-Id: I0f7a813c8da8ce2f60cdcd3d81d854e75556130f
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/192743
Commit-Queue: Erik Ernst <eernst@google.com>
Reviewed-by: Dmitry Stefantsov <dmitryas@google.com>
This commit is contained in:
Erik Ernst 2021-03-25 16:28:34 +00:00 committed by commit-bot@chromium.org
parent 1453eebebc
commit 6db2f7a2f6
17 changed files with 216 additions and 77 deletions

View file

@ -682,7 +682,10 @@ abstract class ClassBuilderImpl extends DeclarationBuilderImpl
TypeAliasBuilder aliasBuilder; // Non-null if a type alias is use.
if (decl is TypeAliasBuilder) {
aliasBuilder = decl;
decl = aliasBuilder.unaliasDeclaration(superClassType.arguments);
decl = aliasBuilder.unaliasDeclaration(superClassType.arguments,
isUsedAsClass: true,
usedAsClassCharOffset: supertypeBuilder.charOffset,
usedAsClassFileUri: superClassType.fileUri);
}
// TODO(eernst): Should gather 'restricted supertype' checks in one place,
// e.g., dynamic/int/String/Null and more are checked elsewhere.
@ -708,7 +711,10 @@ abstract class ClassBuilderImpl extends DeclarationBuilderImpl
TypeAliasBuilder aliasBuilder; // Non-null if a type alias is used.
if (typeDeclaration is TypeAliasBuilder) {
aliasBuilder = typeDeclaration;
decl = aliasBuilder.unaliasDeclaration(type.arguments);
decl = aliasBuilder.unaliasDeclaration(type.arguments,
isUsedAsClass: true,
usedAsClassCharOffset: type.charOffset,
usedAsClassFileUri: type.fileUri);
} else {
decl = typeDeclaration;
}
@ -1149,7 +1155,10 @@ abstract class ClassBuilderImpl extends DeclarationBuilderImpl
if (builder is ClassBuilder) return builder;
if (builder is TypeAliasBuilder) {
TypeDeclarationBuilder declarationBuilder =
builder.unaliasDeclaration(supertype.arguments);
builder.unaliasDeclaration(supertype.arguments,
isUsedAsClass: true,
usedAsClassCharOffset: supertype.charOffset,
usedAsClassFileUri: supertype.fileUri);
if (declarationBuilder is ClassBuilder) return declarationBuilder;
}
}

View file

@ -81,14 +81,14 @@ abstract class TypeAliasBuilder implements TypeDeclarationBuilder {
/// based on the given [typeArguments]. It expands type aliases repeatedly
/// until it encounters a builder which is not a [TypeAliasBuilder].
///
/// If [isInvocation] is false: In this case it is required that
/// If [isUsedAsClass] is false: In this case it is required that
/// `typeArguments.length == typeVariables.length`. The [typeArguments] are
/// threaded through the expansion if needed, and the resulting declaration
/// is returned.
///
/// If [isInvocation] is true: In this case [typeArguments] are ignored, but
/// [invocationCharOffset] and [invocationFileUri] must be non-null. If `this`
/// type alias expands in one or more steps to a builder which is not a
/// If [isUsedAsClass] is true: In this case [typeArguments] are ignored, but
/// [usedAsClassCharOffset] and [usedAsClassFileUri] must be non-null. If
/// `this` type alias expands in one or more steps to a builder which is not a
/// [TypeAliasBuilder] nor a [TypeVariableBuilder] then that builder is
/// returned. If this type alias is cyclic or expands to an invalid type or
/// a type that does not have a declaration (say, a function type) then `this`
@ -97,9 +97,9 @@ abstract class TypeAliasBuilder implements TypeDeclarationBuilder {
/// [TypeVariableBuilder] then the type alias cannot be used in a constructor
/// invocation. Then an error is emitted and `this` is returned.
TypeDeclarationBuilder unaliasDeclaration(List<TypeBuilder> typeArguments,
{bool isInvocation = false,
int invocationCharOffset,
Uri invocationFileUri});
{bool isUsedAsClass = false,
int usedAsClassCharOffset,
Uri usedAsClassFileUri});
/// Compute type arguments passed to [ClassBuilder] from unaliasDeclaration.
/// This method does not check for cycles and may only be called if an
@ -191,25 +191,32 @@ abstract class TypeAliasBuilderImpl extends TypeDeclarationBuilderImpl
/// based on the given [typeArguments]. It expands type aliases repeatedly
/// until it encounters a builder which is not a [TypeAliasBuilder].
///
/// If [isInvocation] is false: In this case it is required that
/// The parameter [isUsedAsClass] indicates whether the type alias is being
/// used as a class, e.g., as the class in an instance creation, as a
/// superinterface, in a redirecting factory constructor, or to invoke a
/// static member.
///
/// If [isUsedAsClass] is false: In this case it is required that
/// `typeArguments.length == typeVariables.length`. The [typeArguments] are
/// threaded through the expansion if needed, and the resulting declaration
/// is returned.
///
/// If [isInvocation] is true: In this case [typeArguments] are ignored, but
/// [invocationCharOffset] and [invocationFileUri] must be non-null. If `this`
/// If [isUsedAsClass] is true: In this case [typeArguments] can be null, but
/// [usedAsClassCharOffset] and [usedAsClassFileUri] must be non-null. When
/// [typeArguments] is null, the returned [TypeDeclarationBuilder] indicates
/// which class the type alias denotes, without type arguments. If `this`
/// type alias expands in one or more steps to a builder which is not a
/// [TypeAliasBuilder] nor a [TypeVariableBuilder] then that builder is
/// returned. If this type alias is cyclic or expands to an invalid type or
/// a type that does not have a declaration (say, a function type) then `this`
/// is returned (when the type was invalid: with `thisType` set to
/// `const InvalidType()`). If `this` type alias expands to a
/// [TypeVariableBuilder] then the type alias cannot be used in a constructor
/// invocation. Then an error is emitted and `this` is returned.
/// [TypeVariableBuilder] then the type alias cannot be used as a class, in
/// which case an error is emitted and `this` is returned.
TypeDeclarationBuilder unaliasDeclaration(List<TypeBuilder> typeArguments,
{bool isInvocation = false,
int invocationCharOffset,
Uri invocationFileUri}) {
{bool isUsedAsClass = false,
int usedAsClassCharOffset,
Uri usedAsClassFileUri}) {
if (_cachedUnaliasedDeclaration != null) return _cachedUnaliasedDeclaration;
Set<TypeDeclarationBuilder> builders = {this};
TypeDeclarationBuilder current = this;
@ -241,15 +248,39 @@ abstract class TypeAliasBuilderImpl extends TypeDeclarationBuilderImpl
// `_cachedUnaliasedDeclaration` because it changes from call to call
// with type aliases of this kind. Note that every `aliasBuilder.type`
// up to this point is a [NamedTypeBuilder], because only they can have
// a non-null `type`. However, a constructor invocation is not admitted.
if (isInvocation) {
library.addProblem(messageTypedefTypeVariableNotConstructor,
invocationCharOffset, noLength, invocationFileUri,
context: [
messageTypedefTypeVariableNotConstructorCause.withLocation(
current.fileUri, current.charOffset, noLength),
]);
return this;
// a non-null `type`. However, this type alias can not be used as a
// class.
if (isUsedAsClass) {
List<TypeBuilder> freshTypeArguments = [
if (typeVariables != null)
for (TypeVariableBuilder typeVariable in typeVariables)
new NamedTypeBuilder.fromTypeDeclarationBuilder(
typeVariable,
library.nonNullableBuilder,
const [],
fileUri,
charOffset,
),
];
TypeDeclarationBuilder typeDeclarationBuilder =
_unaliasDeclaration(freshTypeArguments);
bool found = false;
for (TypeBuilder typeBuilder in freshTypeArguments) {
if (typeBuilder.declaration == typeDeclarationBuilder) {
found = true;
break;
}
}
if (found) {
library.addProblem(messageTypedefTypeVariableNotConstructor,
usedAsClassCharOffset, noLength, usedAsClassFileUri,
context: [
messageTypedefTypeVariableNotConstructorCause.withLocation(
current.fileUri, current.charOffset, noLength),
]);
return this;
}
if (typeArguments == null) return typeDeclarationBuilder;
}
return _unaliasDeclaration(typeArguments);
}

View file

@ -4530,9 +4530,9 @@ class BodyBuilder extends ScopeListener<JumpTarget>
noLength));
}
type = aliasBuilder.unaliasDeclaration(null,
isInvocation: true,
invocationCharOffset: nameToken.charOffset,
invocationFileUri: uri);
isUsedAsClass: true,
usedAsClassCharOffset: nameToken.charOffset,
usedAsClassFileUri: uri);
List<TypeBuilder> typeArgumentBuilders = [];
if (typeArguments != null) {
for (UnresolvedType unresolvedType in typeArguments) {

View file

@ -1374,7 +1374,10 @@ class ClassHierarchyNodeBuilder {
if (mixin is TypeAliasBuilder) {
TypeAliasBuilder aliasBuilder = mixin;
NamedTypeBuilder namedBuilder = mixedInTypeBuilder;
mixin = aliasBuilder.unaliasDeclaration(namedBuilder.arguments);
mixin = aliasBuilder.unaliasDeclaration(namedBuilder.arguments,
isUsedAsClass: true,
usedAsClassCharOffset: namedBuilder.charOffset,
usedAsClassFileUri: namedBuilder.fileUri);
}
if (mixin is ClassBuilder) {
scope = mixin.scope.computeMixinScope();

View file

@ -3061,9 +3061,9 @@ class TypeUseGenerator extends ReadOnlyAccessGenerator {
if (declarationBuilder is TypeAliasBuilder) {
TypeAliasBuilder aliasBuilder = declarationBuilder;
declarationBuilder = aliasBuilder.unaliasDeclaration(null,
isInvocation: true,
invocationCharOffset: this.fileOffset,
invocationFileUri: _uri);
isUsedAsClass: true,
usedAsClassCharOffset: this.fileOffset,
usedAsClassFileUri: _uri);
}
if (declarationBuilder is DeclarationBuilder) {
DeclarationBuilder declaration = declarationBuilder;

View file

@ -675,7 +675,10 @@ class KernelTarget extends TargetImplementation {
if (supertype is TypeAliasBuilder) {
TypeAliasBuilder aliasBuilder = supertype;
NamedTypeBuilder namedBuilder = type;
supertype = aliasBuilder.unaliasDeclaration(namedBuilder.arguments);
supertype = aliasBuilder.unaliasDeclaration(namedBuilder.arguments,
isUsedAsClass: true,
usedAsClassCharOffset: namedBuilder.charOffset,
usedAsClassFileUri: namedBuilder.fileUri);
}
if (supertype is SourceClassBuilder && supertype.isMixinApplication) {
installForwardingConstructors(supertype);

View file

@ -589,8 +589,11 @@ class SourceClassBuilder extends ClassBuilderImpl
if (declarationBuilder is TypeAliasBuilder) {
TypeAliasBuilder aliasBuilder = declarationBuilder;
NamedTypeBuilder namedBuilder = supertype;
declarationBuilder =
aliasBuilder.unaliasDeclaration(namedBuilder.arguments);
declarationBuilder = aliasBuilder.unaliasDeclaration(
namedBuilder.arguments,
isUsedAsClass: true,
usedAsClassCharOffset: namedBuilder.charOffset,
usedAsClassFileUri: namedBuilder.fileUri);
result[declarationBuilder] = aliasBuilder;
} else {
result[declarationBuilder] = null;
@ -606,8 +609,11 @@ class SourceClassBuilder extends ClassBuilderImpl
if (declarationBuilder is TypeAliasBuilder) {
TypeAliasBuilder aliasBuilder = declarationBuilder;
NamedTypeBuilder namedBuilder = interface;
declarationBuilder =
aliasBuilder.unaliasDeclaration(namedBuilder.arguments);
declarationBuilder = aliasBuilder.unaliasDeclaration(
namedBuilder.arguments,
isUsedAsClass: true,
usedAsClassCharOffset: namedBuilder.charOffset,
usedAsClassFileUri: namedBuilder.fileUri);
result[declarationBuilder] = aliasBuilder;
} else {
result[declarationBuilder] = null;
@ -621,8 +627,11 @@ class SourceClassBuilder extends ClassBuilderImpl
if (declarationBuilder is TypeAliasBuilder) {
TypeAliasBuilder aliasBuilder = declarationBuilder;
NamedTypeBuilder namedBuilder = mixedInTypeBuilder;
declarationBuilder =
aliasBuilder.unaliasDeclaration(namedBuilder.arguments);
declarationBuilder = aliasBuilder.unaliasDeclaration(
namedBuilder.arguments,
isUsedAsClass: true,
usedAsClassCharOffset: namedBuilder.charOffset,
usedAsClassFileUri: namedBuilder.fileUri);
result[declarationBuilder] = aliasBuilder;
} else {
result[declarationBuilder] = null;

View file

@ -907,7 +907,10 @@ class SourceLoader extends Loader {
if (builder is TypeAliasBuilder) {
TypeAliasBuilder aliasBuilder = builder;
NamedTypeBuilder namedBuilder = mixedInTypeBuilder;
builder = aliasBuilder.unaliasDeclaration(namedBuilder.arguments);
builder = aliasBuilder.unaliasDeclaration(namedBuilder.arguments,
isUsedAsClass: true,
usedAsClassCharOffset: namedBuilder.charOffset,
usedAsClassFileUri: namedBuilder.fileUri);
if (builder is! ClassBuilder) {
cls.addProblem(
templateIllegalMixin.withArguments(builder.fullNameForErrors),

View file

@ -21,22 +21,45 @@ abstract class C {
// [cfe] unspecified
}
class D1<X> extends T<X> {}
// ^
class D {}
mixin M {}
abstract class D1<X> extends T<D> {}
// ^
// [analyzer] unspecified
// [cfe] unspecified
abstract class D2 extends C with T<int> {}
abstract class D2 extends C with T<M> {}
// ^
// [analyzer] unspecified
// [cfe] unspecified
abstract class D3<X, Y> implements T<T> {}
abstract class D3<X, Y> implements T<T<D>> {}
// ^
// [analyzer] unspecified
// [cfe] unspecified
abstract class D4 = C with T<void>;
abstract class D4 = C with T<D>;
// ^
// [analyzer] unspecified
// [cfe] unspecified
class D5<X> extends T<X> {}
// ^
// [analyzer] unspecified
// [cfe] unspecified
abstract class D6 extends C with T<int> {}
// ^
// [analyzer] unspecified
// [cfe] unspecified
abstract class D7<X, Y> implements T<T> {}
// ^
// [analyzer] unspecified
// [cfe] unspecified
abstract class D8 = C with T<void>;
// ^
// [analyzer] unspecified
// [cfe] unspecified

View file

@ -45,12 +45,6 @@ class C1 implements C {
}
class D {}
mixin M {}
abstract class D1<X> extends T<D> {}
abstract class D2 extends C with T<M> {}
abstract class D3<X, Y> implements T<T<D>> {}
abstract class D4 = C with T<D>;
extension E on T<dynamic> {
T<dynamic> foo(T<dynamic> t) => t;

View file

@ -23,22 +23,45 @@ abstract class C {
// [cfe] unspecified
}
class D1<X> extends T<X> {}
// ^
class D {}
mixin M {}
abstract class D1<X> extends T<D> {}
// ^
// [analyzer] unspecified
// [cfe] unspecified
abstract class D2 extends C with T<int> {}
abstract class D2 extends C with T<M> {}
// ^
// [analyzer] unspecified
// [cfe] unspecified
abstract class D3<X, Y> implements T<T> {}
abstract class D3<X, Y> implements T<T<D>> {}
// ^
// [analyzer] unspecified
// [cfe] unspecified
abstract class D4 = C with T<void>;
abstract class D4 = C with T<D>;
// ^
// [analyzer] unspecified
// [cfe] unspecified
class D5<X> extends T<X> {}
// ^
// [analyzer] unspecified
// [cfe] unspecified
abstract class D6 extends C with T<int> {}
// ^
// [analyzer] unspecified
// [cfe] unspecified
abstract class D7<X, Y> implements T<T> {}
// ^
// [analyzer] unspecified
// [cfe] unspecified
abstract class D8 = C with T<void>;
// ^
// [analyzer] unspecified
// [cfe] unspecified

View file

@ -47,16 +47,6 @@ class C1 implements C {
class D {}
mixin M {}
abstract class D1<X> extends T<D> {}
abstract class D2 extends C with T<M> {}
abstract class D3<X, Y> implements T<T<D>> {}
abstract class D4 = C with T<D>;
extension E on T<dynamic> {
T<dynamic> foo(T<dynamic> t) => t;
}

View file

@ -22,16 +22,28 @@ class C {
// [cfe] unspecified
}
class D1 extends T {}
// ^
// [analyzer] unspecified
// [cfe] unspecified
abstract class D2 extends C with T {}
// ^
// [analyzer] unspecified
// [cfe] unspecified
abstract class D3 implements T {}
// ^
// [analyzer] unspecified
// [cfe] unspecified
abstract class D4 = C with T;
// ^
// [analyzer] unspecified
// [cfe] unspecified
X foo<X>(X x) => x;
main() {
var v14 = <Set<T>, Set<T>>{{}: {}};
v14[{}] = {T()};

View file

@ -45,10 +45,6 @@ class C1 implements C {
noSuchMethod(Invocation invocation) => throw 0;
}
class D1 extends T {}
abstract class D3 implements T {}
extension E on T {
T foo(T t) => t;
}
@ -60,7 +56,6 @@ T Function(T) id = (x) => x;
main() {
var v13 = <T>[];
var v14 = <Set<T>, Set<T>>{{}: {}};
v14[{}] = {D1()};
var v15 = {v13};
Set<List<T>> v16 = v15;
v15 = v16;

View file

@ -0,0 +1,35 @@
// Copyright (c) 2021, 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.
// SharedOptions=--enable-experiment=nonfunction-type-aliases
import 'package:expect/expect.dart';
typedef TB<T extends C> = T;
typedef AC = C; // Direct.
typedef AEC = TB<C>; // Explicit C argument.
typedef AIC = TB; // Implicit instantiate to bounds.
class C {
static const c = 42;
static int s = 42;
final int y;
const C(this.y);
const C.name(this.y);
}
main() {
const c0 = AC.c;
const c1 = AEC.c;
const c2 = AIC.c;
Expect.equals(42, AC.s);
Expect.equals(42, AEC.s);
Expect.equals(42, AIC.s);
Expect.equals(0, AC(0).y);
Expect.equals(0, AEC(0).y);
Expect.equals(0, AIC(0).y);
Expect.equals(0, AC.name(0).y);
Expect.equals(0, AEC.name(0).y);
Expect.equals(0, AIC.name(0).y);
}

View file

@ -25,16 +25,29 @@ class C {
// [cfe] unspecified
}
class D1 extends T {}
// ^
// [analyzer] unspecified
// [cfe] unspecified
abstract class D2 extends C with T {}
// ^
// [analyzer] unspecified
// [cfe] unspecified
abstract class D3 implements T {}
// ^
// [analyzer] unspecified
// [cfe] unspecified
abstract class D4 = C with T;
// ^
// [analyzer] unspecified
// [cfe] unspecified
X foo<X>(X x) => x;
main() {
var v14 = <Set<T>, Set<T>>{{}: {}};
v14[{}] = {T()};

View file

@ -48,9 +48,6 @@ class C1 implements C {
noSuchMethod(Invocation invocation) => throw 0;
}
class D1 extends T {}
abstract class D3 implements T {}
extension E on T {
T foo(T t) => t;
}
@ -62,7 +59,6 @@ T Function(T) id = (x) => x;
main() {
var v13 = <T>[];
var v14 = <Set<T>, Set<T>>{{}: {}};
v14[{}] = {D1()};
var v15 = {v13};
Set<List<T>> v16 = v15;
v15 = v16;