blob: b1fee9cb08820d145cf7128a08c43ff05b61d6ad [file] [log] [blame]
// Copyright (c) 2016, 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.
part of 'declaration_builders.dart';
// Coverage-ignore(suite): Not run.
abstract class TypeAliasBuilder implements TypeDeclarationBuilder {
TypeBuilder get type;
/// The [Typedef] built by this builder.
///
/// For a typedef built from source, this is only available after AST nodes
/// have been built in [SourceLoader.buildOutlineNodes].
Typedef get typedef;
/// Reference for the typedef built by this builder.
Reference get reference;
DartType? thisType;
String get debugName;
@override
LibraryBuilder get parent;
LibraryBuilder get libraryBuilder;
@override
Uri get fileUri;
List<NominalVariableBuilder>? get typeVariables;
bool get fromDill => false;
DartType buildThisType();
List<DartType> buildAliasedTypeArguments(LibraryBuilder library,
List<TypeBuilder>? arguments, ClassHierarchyBase? hierarchy);
/// Returns the unaliased type for this type alias with the given
/// [typeArguments], or `null` if this type alias is cyclic.
///
/// If [usedTypeAliasBuilders] is supplied, the [TypeAliasBuilder]s used
/// during unaliasing are added to [usedTypeAliasBuilders].
///
/// If [unboundTypes] is provided, created type builders that are not bound
/// are added to [unboundTypes]. Otherwise, creating an unbound type builder
/// results in an assertion error.
///
/// If [unboundTypeVariables] is provided, created type variable builders are
/// added to [unboundTypeVariables]. Otherwise, creating a
/// type variable builder result in an assertion error.
///
/// The [unboundTypes] and [unboundTypeVariables] must be processed by the
/// call, unless the created [TypeBuilder]s and [TypeVariableBuilder]s are
/// not part of the generated AST.
// TODO(johnniwinther): Used this instead of [unaliasDeclaration] and
// [unaliasTypeArguments].
TypeBuilder? unalias(List<TypeBuilder>? typeArguments,
{Set<TypeAliasBuilder>? usedTypeAliasBuilders,
List<StructuralVariableBuilder>? unboundTypeVariables});
/// Returns the [TypeDeclarationBuilder] for the type aliased by `this`,
/// based on the given [typeArguments]. It expands type aliases repeatedly
/// until it encounters a builder which is not a [TypeAliasBuilder].
///
/// 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 [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`
/// 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.
TypeDeclarationBuilder? unaliasDeclaration(List<TypeBuilder>? typeArguments,
{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
/// invocation of `this.unaliasDeclaration(typeArguments)` has returned a
/// [ClassBuilder].
///
/// The parameter [typeArguments] would typically be obtained from a
/// [NamedTypeBuilder] whose `declaration` is `this`. It must be non-null.
///
/// Returns `null` if an error occurred.
///
/// The method substitutes through the chain of type aliases denoted by
/// this [TypeAliasBuilder], such that the returned [TypeBuilder]s are
/// appropriate type arguments for passing to the [ClassBuilder] which is the
/// end of the unaliasing chain.
// TODO(johnniwinther): Should we enforce that [typeArguments] are non-null
// as stated in the docs? It is not needed for the implementation.
List<TypeBuilder>? unaliasTypeArguments(List<TypeBuilder>? typeArguments);
/// Returns the lowering for the constructor or factory named [name] on the
/// effective target class of this typedef.
///
/// For instance, if we have
///
/// class A<T> {
/// A();
/// }
/// typedef F = A<int>;
/// typedef G = F;
/// typedef H<X, Y> = A<X>;
///
/// the lowering will create
///
/// A<int> _#F#new#tearOff() => new A<int>();
/// A<int> _#G#new#tearOff() => new A<int>();
/// A<int> _#H#new#tearOff<X, Y>() => new A<X>();
///
/// which will be return by [findConstructorOrFactory] on `F`, `G`, `H` with
/// name 'new' or ''.
Procedure? findConstructorOrFactory(
String name, int charOffset, Uri uri, LibraryBuilder accessingLibrary);
}
abstract class TypeAliasBuilderImpl extends TypeDeclarationBuilderImpl
implements TypeAliasBuilder {
@override
final Uri fileUri;
TypeBuilder? _unaliasedRhsType;
List<TypeAliasBuilder> _typeAliasesUsedInUnaliasing = [];
TypeAliasBuilderImpl(
String name, LibraryBuilder parent, this.fileUri, int fileOffset)
: super(name, parent, fileOffset);
@override
String get debugName => "TypeAliasBuilder";
@override
LibraryBuilder get parent => super.parent as LibraryBuilder;
@override
LibraryBuilder get libraryBuilder => super.parent as LibraryBuilder;
/// [arguments] have already been built.
@override
DartType buildAliasedTypeWithBuiltArguments(
LibraryBuilder library,
Nullability nullability,
List<DartType>? arguments,
TypeUse typeUse,
Uri fileUri,
int charOffset,
{required bool hasExplicitTypeArguments}) {
buildThisType();
TypedefType type = new TypedefType(typedef, nullability, arguments);
if (library is SourceLibraryBuilder) {
if (typeVariablesCount != 0) {
library.registerBoundsCheck(type, fileUri, charOffset, typeUse,
inferred: !hasExplicitTypeArguments);
}
if (!library.libraryFeatures.genericMetadata.isEnabled) {
library.registerGenericFunctionTypeCheck(type, fileUri, charOffset);
}
}
return type;
}
@override
DartType buildAliasedType(
LibraryBuilder library,
NullabilityBuilder nullabilityBuilder,
List<TypeBuilder>? arguments,
TypeUse typeUse,
Uri fileUri,
int charOffset,
ClassHierarchyBase? hierarchy,
{required bool hasExplicitTypeArguments}) {
DartType thisType = buildThisType();
if (thisType is InvalidType) return thisType;
Nullability nullability = nullabilityBuilder.build();
return buildAliasedTypeWithBuiltArguments(
library,
nullability,
buildAliasedTypeArguments(library, arguments, hierarchy),
typeUse,
fileUri,
charOffset,
hasExplicitTypeArguments: hasExplicitTypeArguments);
}
void _ensureUnaliasedType(
{required List<StructuralVariableBuilder>? unboundTypeVariables}) {
if (_unaliasedRhsType != null) {
return;
}
_typeAliasesUsedInUnaliasing.add(this);
TypeDeclarationBuilder? rhsTypeDeclaration = type.declaration;
switch (rhsTypeDeclaration) {
case ClassBuilder():
case ExtensionTypeDeclarationBuilder():
case NominalVariableBuilder():
case StructuralVariableBuilder():
case BuiltinTypeDeclarationBuilder():
case OmittedTypeDeclarationBuilder():
case InvalidTypeDeclarationBuilder():
case null:
_unaliasedRhsType = type;
case TypeAliasBuilder():
rhsTypeDeclaration as TypeAliasBuilderImpl;
Set<TypeAliasBuilder> usedTypeAliasBuilders = {};
List<NominalVariableBuilder>? typeVariables =
rhsTypeDeclaration.typeVariables;
List<TypeBuilder>? typeArguments = type.typeArguments;
TypeBuilder? unaliasedRhsType = rhsTypeDeclaration.unalias(
typeArguments,
usedTypeAliasBuilders: usedTypeAliasBuilders,
unboundTypeVariables: unboundTypeVariables);
_unaliasedRhsType = unaliasedRhsType;
if (typeVariables != null) {
if (typeArguments == null ||
typeVariables.length != typeArguments.length) {
// Coverage-ignore-block(suite): Not run.
typeArguments = <TypeBuilder>[
for (NominalVariableBuilder typeVariable in typeVariables)
typeVariable.defaultType!
];
}
_unaliasedRhsType = _unaliasedRhsType
?.subst(
new Map<NominalVariableBuilder, TypeBuilder>.fromIterables(
typeVariables, typeArguments))
.unalias(
usedTypeAliasBuilders: usedTypeAliasBuilders,
unboundTypeVariables: unboundTypeVariables);
}
_typeAliasesUsedInUnaliasing.addAll(usedTypeAliasBuilders);
// Coverage-ignore(suite): Not run.
case ExtensionBuilder():
unexpected(
"type", "${rhsTypeDeclaration.runtimeType}", charOffset, fileUri);
}
}
@override
TypeBuilder? unalias(List<TypeBuilder>? typeArguments,
{Set<TypeAliasBuilder>? usedTypeAliasBuilders,
List<StructuralVariableBuilder>? unboundTypeVariables}) {
_ensureUnaliasedType(unboundTypeVariables: unboundTypeVariables);
if (usedTypeAliasBuilders != null) {
usedTypeAliasBuilders.addAll(_typeAliasesUsedInUnaliasing);
}
TypeBuilder? unaliasedRhsType = _unaliasedRhsType;
TypeDeclarationBuilder? rhsDeclaration = unaliasedRhsType?.declaration;
switch (rhsDeclaration) {
case TypeAliasBuilder():
// At this point the unaliased right-hand side type can't be a type
// alias, which is ensured by the call to [_ensureUnaliasedType]
// earlier.
return unexpected(
"unaliased", "${rhsDeclaration.runtimeType}", charOffset, fileUri);
case ClassBuilder():
case ExtensionTypeDeclarationBuilder():
case NominalVariableBuilder():
case StructuralVariableBuilder():
List<NominalVariableBuilder>? typeVariables = this.typeVariables;
if (typeVariables != null) {
if (typeArguments == null ||
typeVariables.length != typeArguments.length) {
typeArguments = <TypeBuilder>[
for (NominalVariableBuilder typeVariable in typeVariables)
typeVariable.defaultType!
];
}
return unaliasedRhsType!.subst(
new Map<NominalVariableBuilder, TypeBuilder>.fromIterables(
typeVariables, typeArguments),
unboundTypeVariables: unboundTypeVariables);
}
case ExtensionBuilder():
case BuiltinTypeDeclarationBuilder():
case InvalidTypeDeclarationBuilder():
case OmittedTypeDeclarationBuilder():
// These types won't change after substitution, so we just return them.
return unaliasedRhsType;
case null:
}
return type;
}
TypeDeclarationBuilder? _cachedUnaliasedDeclaration;
/// Returns the [TypeDeclarationBuilder] for the type aliased by `this`,
/// based on the given [typeArguments]. It expands type aliases repeatedly
/// until it encounters a builder which is not a [TypeAliasBuilder].
///
/// 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 [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 as a class, in
/// which case an error is emitted and `this` is returned.
@override
TypeDeclarationBuilder? unaliasDeclaration(List<TypeBuilder>? typeArguments,
{bool isUsedAsClass = false,
int? usedAsClassCharOffset,
Uri? usedAsClassFileUri}) {
if (_cachedUnaliasedDeclaration != null) {
return _cachedUnaliasedDeclaration;
}
Set<TypeDeclarationBuilder> builders = {this};
TypeDeclarationBuilder current = this;
while (current is TypeAliasBuilder) {
TypeAliasBuilder currentAliasBuilder = current;
TypeDeclarationBuilder? next = currentAliasBuilder.type.declaration;
if (next != null) {
current = next;
} else {
// `currentAliasBuilder`'s right hand side is not a [NamedTypeBuilder].
// There is no ultimate declaration, so unaliasing is a no-op.
return _cachedUnaliasedDeclaration = this;
}
if (builders.contains(current)) {
// Coverage-ignore-block(suite): Not run.
// Cyclic type alias.
currentAliasBuilder.libraryBuilder.addProblem(
templateCyclicTypedef.withArguments(this.name),
charOffset,
noLength,
fileUri);
// Ensure that it is not reported again.
thisType = const InvalidType();
return _cachedUnaliasedDeclaration = this;
}
if (current is NominalVariableBuilder) {
// Encountered `typedef F<..X..> = X`, must repeat the computation,
// tracing type variables at each step. We repeat everything because
// that kind of type alias is expected to be rare. We cannot save it in
// `_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, this type alias can not be used as a
// class.
if (isUsedAsClass) {
List<TypeBuilder> freshTypeArguments = [
if (typeVariables != null)
for (NominalVariableBuilder typeVariable in typeVariables!)
new NamedTypeBuilderImpl.fromTypeDeclarationBuilder(
typeVariable, const NullabilityBuilder.omitted(),
arguments: const [],
fileUri: fileUri,
charOffset: charOffset,
instanceTypeVariableAccess:
InstanceTypeVariableAccessState.Unexpected),
];
TypeDeclarationBuilder? typeDeclarationBuilder =
_unaliasDeclaration(freshTypeArguments);
bool found = false;
for (TypeBuilder typeBuilder in freshTypeArguments) {
if (typeBuilder.declaration == typeDeclarationBuilder) {
found = true;
break;
}
}
if (found) {
libraryBuilder.addProblem(
messageTypedefTypeVariableNotConstructor,
usedAsClassCharOffset ?? TreeNode.noOffset,
noLength,
usedAsClassFileUri,
context: [
messageTypedefTypeVariableNotConstructorCause.withLocation(
current.fileUri!, current.charOffset, noLength),
]);
return this;
}
if (typeArguments == null) return typeDeclarationBuilder;
}
return _unaliasDeclaration(typeArguments);
}
}
return _cachedUnaliasedDeclaration = current;
}
// Helper method with same purpose as [unaliasDeclaration], for a hard case.
//
// It is required that `typeArguments.length == typeVariables.length`, and
// [typeArguments] are considered to be passed as actual type arguments to
// [this]. It is also required that the sequence traversed by following
// `.type.declaration` starting from `this` in a finite number of steps
// reaches a `TypeVariableBuilder`. So this method does not check for cycles,
// nor for other types than `NamedTypeBuilder` and `TypeVariableBuilder`
// after each step over a `.type.declaration`.
//
// Returns `this` if an error is encountered.
//
// This method more expensive than [unaliasDeclaration], but it will handle
// the case where a sequence of type aliases F_1 .. F_k is such that F_i
// has a right hand side which is F_{i+1}, possibly applied to some type
// arguments, for all i in 1 .. k-1, and the right hand side of F_k is a type
// variable. In this case, the unaliased declaration must be obtained from
// the type argument, which is the reason why we must trace them.
TypeDeclarationBuilder? _unaliasDeclaration(
List<TypeBuilder>? typeArguments) {
TypeDeclarationBuilder? currentDeclarationBuilder = this;
TypeAliasBuilder? previousAliasBuilder = null;
List<TypeBuilder>? currentTypeArguments = typeArguments;
while (currentDeclarationBuilder is TypeAliasBuilder) {
TypeAliasBuilder currentAliasBuilder = currentDeclarationBuilder;
TypeBuilder nextTypeBuilder = currentAliasBuilder.type;
Map<NominalVariableBuilder, TypeBuilder> substitution = {};
int index = 0;
if (currentTypeArguments == null || currentTypeArguments.isEmpty) {
if (currentAliasBuilder.typeVariables != null) {
List<TypeBuilder> defaultTypeArguments =
new List<TypeBuilder>.generate(
currentAliasBuilder.typeVariables!.length, (int i) {
return currentAliasBuilder.typeVariables![i].defaultType!;
}, growable: true);
currentTypeArguments = defaultTypeArguments;
} else {
currentTypeArguments = <TypeBuilder>[];
}
}
if ((currentAliasBuilder.typeVariables?.length ?? 0) !=
currentTypeArguments.length) {
// Coverage-ignore-block(suite): Not run.
if (previousAliasBuilder != null) {
previousAliasBuilder.libraryBuilder.addProblem(
templateTypeArgumentMismatch.withArguments(
currentAliasBuilder.typeVariables?.length ?? 0),
previousAliasBuilder.charOffset,
noLength,
previousAliasBuilder.fileUri);
previousAliasBuilder.thisType = const InvalidType();
return this;
} else {
// This implies that `currentAliasBuilder` is [this], and the call
// violated the precondition.
return unhandled("$this: Wrong number of type arguments",
"_unaliasDeclaration", -1, null);
}
}
for (NominalVariableBuilder typeVariableBuilder
in currentAliasBuilder.typeVariables ?? []) {
substitution[typeVariableBuilder] = currentTypeArguments[index];
++index;
}
TypeDeclarationBuilder? nextDeclarationBuilder =
nextTypeBuilder.declaration;
TypeBuilder substitutedBuilder = nextTypeBuilder.subst(substitution);
if (nextDeclarationBuilder is NominalVariableBuilder) {
// We have reached the end of the type alias chain which yields a
// type argument, which may become a type alias, possibly with its
// own similar chain. We do not simply continue the iteration here,
// though: We must call `unaliasDeclaration` because it can be
// cyclic; we want to do it as well, because the result could be
// cached.
return substitutedBuilder.computeUnaliasedDeclaration(
isUsedAsClass: false);
}
// Not yet at the end of the chain, more named builders to come.
currentDeclarationBuilder = substitutedBuilder.declaration;
currentTypeArguments = substitutedBuilder.typeArguments;
previousAliasBuilder = currentAliasBuilder;
}
return currentDeclarationBuilder;
}
/// Compute type arguments passed to [ClassBuilder] or
/// [ExtensionTypeDeclarationBuilder] from unaliasDeclaration.
/// This method does not check for cycles and may only be called if an
/// invocation of `this.unaliasDeclaration(typeArguments)` has returned a
/// [ClassBuilder] or [ExtensionTypeDeclarationBuilder].
///
/// The parameter [typeArguments] would typically be obtained from a
/// [NamedTypeBuilder] whose `declaration` is `this`. It must be non-null.
///
/// Returns `null` if an error occurred.
///
/// The method substitutes through the chain of type aliases denoted by
/// this [TypeAliasBuilderImpl], such that the returned [TypeBuilder]s are
/// appropriate type arguments for passing to the [ClassBuilder] or
/// [ExtensionTypeDeclarationBuilder] which is the end of the unaliasing
/// chain.
@override
List<TypeBuilder>? unaliasTypeArguments(List<TypeBuilder>? typeArguments) {
TypeDeclarationBuilder? currentDeclarationBuilder = this;
List<TypeBuilder>? currentTypeArguments = typeArguments;
while (currentDeclarationBuilder is TypeAliasBuilder) {
TypeAliasBuilder currentAliasBuilder = currentDeclarationBuilder;
TypeBuilder nextTypeBuilder = currentAliasBuilder.type;
Map<NominalVariableBuilder, TypeBuilder> substitution = {};
int index = 0;
if (currentTypeArguments == null || currentTypeArguments.isEmpty) {
if (currentAliasBuilder.typeVariables != null) {
// Coverage-ignore-block(suite): Not run.
List<TypeBuilder> defaultTypeArguments =
new List<TypeBuilder>.generate(
currentAliasBuilder.typeVariables!.length, (int i) {
return currentAliasBuilder.typeVariables![i].defaultType!;
}, growable: false);
currentTypeArguments = defaultTypeArguments;
} else {
currentTypeArguments = <TypeBuilder>[];
}
}
assert((currentAliasBuilder.typeVariables?.length ?? 0) ==
currentTypeArguments.length);
for (NominalVariableBuilder typeVariableBuilder
in currentAliasBuilder.typeVariables ?? []) {
substitution[typeVariableBuilder] = currentTypeArguments[index];
++index;
}
TypeDeclarationBuilder? nextDeclarationBuilder =
nextTypeBuilder.declaration;
TypeBuilder substitutedBuilder = nextTypeBuilder.subst(substitution);
if (nextDeclarationBuilder is NominalVariableBuilder) {
// Coverage-ignore-block(suite): Not run.
// We have reached the end of the type alias chain which yields a
// type argument, which may become a type alias, possibly with its
// own similar chain.
TypeDeclarationBuilder? declarationBuilder =
substitutedBuilder.declaration;
if (declarationBuilder is TypeAliasBuilder) {
return declarationBuilder
.unaliasTypeArguments(substitutedBuilder.typeArguments);
}
assert(declarationBuilder is ClassBuilder ||
declarationBuilder is ExtensionTypeDeclarationBuilder);
return substitutedBuilder.typeArguments ?? [];
}
// Not yet at the end of the chain, more named builders to come.
currentDeclarationBuilder = substitutedBuilder.declaration;
currentTypeArguments = substitutedBuilder.typeArguments ?? [];
}
return currentTypeArguments;
}
Map<Name, Procedure>? get tearOffs;
@override
Procedure? findConstructorOrFactory(
String text, int charOffset, Uri uri, LibraryBuilder accessingLibrary) {
if (tearOffs != null) {
Name name = new Name(text == 'new' ? '' : text, accessingLibrary.library);
return tearOffs![name];
}
return null;
}
@override
Nullability computeNullabilityWithArguments(List<TypeBuilder>? typeArguments,
{required Map<TypeVariableBuilder, TraversalState>
typeVariablesTraversalState}) {
return unalias(typeArguments, unboundTypeVariables: [])!.computeNullability(
typeVariablesTraversalState: typeVariablesTraversalState);
}
}
/// Used to detect cycles in the declaration of a typedef
///
/// When a typedef is built, [pendingTypeAliasMarker] is used as a placeholder
/// value to indicated that the process has started. If somewhere in the
/// process of building the typedef this value is encountered, it's replaced
/// with [cyclicTypeAliasMarker] as the result of the build process.
final InvalidType pendingTypeAliasMarker = new InvalidType();
/// Used to detect cycles in the declaration of a typedef
///
/// When a typedef is built, [pendingTypeAliasMarker] is used as a placeholder
/// value to indicated that the process has started. If somewhere in the
/// process of building the typedef this value is encountered, it's replaced
/// with [cyclicTypeAliasMarker] as the result of the build process.
final InvalidType cyclicTypeAliasMarker = new InvalidType();