blob: d223bd4b4904237b9c9439562f3cbf170d633245 [file]
// 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.
import 'package:_fe_analyzer_shared/src/parser/formal_parameter_kind.dart'
show FormalParameterKind;
import 'package:_fe_analyzer_shared/src/scanner/scanner.dart' show Token;
import 'package:kernel/ast.dart'
show DartType, DynamicType, Expression, InvalidExpression;
import 'package:kernel/class_hierarchy.dart';
import '../base/extension_scope.dart';
import '../base/lookup_result.dart';
import '../base/modifiers.dart';
import '../base/scope.dart' show LookupScope;
import '../kernel/body_builder_context.dart';
import '../kernel/external_ast_helper.dart' as extern;
import '../kernel/internal_ast.dart'
show
InternalVariable,
InternalFunctionParameter,
InternalCatchVariable,
InternalAnonymousMethodParameter;
import '../kernel/internal_ast_helper.dart' as intern;
import '../kernel/resolver.dart';
import '../kernel/wildcard_lowering.dart';
import '../source/fragment_factory.dart';
import '../source/source_constructor_builder.dart';
import '../source/source_factory_builder.dart';
import '../source/source_library_builder.dart';
import '../source/source_member_builder.dart';
import '../source/source_property_builder.dart';
import '../util/helpers.dart';
import 'builder.dart';
import 'declaration_builders.dart';
import 'metadata_builder.dart';
import 'omitted_type_builder.dart';
import 'property_builder.dart';
import 'type_builder.dart';
import 'variable_builder.dart';
/// A builder for an anonymous method parameter.
class AnonymousMethodParameterBuilder extends NamedBuilderImpl
with LookupResultMixin
implements ParameterVariableBuilder, InferredTypeListener {
@override
final int fileOffset;
final Modifiers modifiers;
@override
TypeBuilder type;
@override
final String name;
@override
final Uri fileUri;
@override
final FormalParameterKind kind;
/// The variable declaration created for this parameter.
InternalAnonymousMethodParameter? _variable;
/// If this is a wildcard variable, this holds the index used to create a
/// uniquely named kernel variable for it.
final int? _wildcardIndex;
final int? nameOffset;
new({
required this.modifiers,
required this.type,
required this.name,
required this.fileOffset,
required this.fileUri,
Token? defaultValueToken,
int? wildcardIndex,
required this.nameOffset,
required this.kind,
}) : this._wildcardIndex = wildcardIndex {
type.registerInferredTypeListener(this);
}
@override
// Coverage-ignore(suite): Not run.
String get fullNameForErrors => name;
@override
NamedBuilder get getable => this;
@override
bool get isAssignable => false;
@override
bool get isConst => false;
@override
// Coverage-ignore(suite): Not run.
bool get isFinal => true;
@override
// Coverage-ignore(suite): Not run.
bool get isLate => false;
@override
bool get isPrimaryConstructorParameter => false;
@override
bool get isWildcard => _wildcardIndex != null;
@override
// Coverage-ignore(suite): Not run.
Builder? get parent => null;
@override
NamedBuilder? get setable => isAssignable ? this : null;
@override
InternalAnonymousMethodParameter get variable => _variable!;
@override
InternalAnonymousMethodParameter build(SourceLibraryBuilder library) {
if (_variable == null) {
bool isTypeOmitted = type is OmittedTypeBuilder;
DartType? builtType = type.build(library, TypeUse.parameterType);
String variableName = _wildcardIndex != null
?
// Coverage-ignore(suite): Not run.
createWildcardFormalParameterName(_wildcardIndex)
: name;
_variable = intern.createAnonymousMethodParameter(
name: variableName,
type: builtType,
isWildcard: isWildcard,
fileOffset: fileOffset,
isFinal: modifiers.isFinal,
isSynthesized: false,
isImplicitlyTyped: isTypeOmitted,
);
}
return _variable!;
}
@override
// Coverage-ignore(suite): Not run.
void onInferredType(DartType type) {
if (_variable != null) {
_variable!.type = type;
}
}
@override
String toString() => '$runtimeType($name)';
}
/// A builder for a catch block parameter.
class CatchParameterBuilder extends NamedBuilderImpl
with LookupResultMixin
implements ParameterVariableBuilder, InferredTypeListener {
@override
final int fileOffset;
final Modifiers modifiers;
@override
TypeBuilder type;
@override
final String name;
@override
final Uri fileUri;
/// The variable declaration created for this catch parameter.
InternalCatchVariable? _variable;
/// If this is a wildcard variable, this holds the index used to create a
/// uniquely named kernel variable for it.
final int? _wildcardIndex;
final int? nameOffset;
new({
required this.modifiers,
required this.type,
required this.name,
required this.fileOffset,
required this.fileUri,
Token? defaultValueToken,
int? wildcardIndex,
required this.nameOffset,
}) : this._wildcardIndex = wildcardIndex {
type.registerInferredTypeListener(this);
}
@override
// Coverage-ignore(suite): Not run.
String get fullNameForErrors => name;
@override
NamedBuilder get getable => this;
@override
bool get isAssignable => false;
@override
bool get isConst => false;
@override
// Coverage-ignore(suite): Not run.
bool get isFinal => true;
@override
// Coverage-ignore(suite): Not run.
bool get isLate => false;
@override
bool get isPrimaryConstructorParameter => false;
@override
bool get isWildcard => _wildcardIndex != null;
@override
FormalParameterKind get kind {
throw new UnsupportedError("${this.runtimeType}.kind");
}
@override
// Coverage-ignore(suite): Not run.
Builder? get parent => null;
@override
NamedBuilder? get setable => isAssignable ? this : null;
@override
InternalCatchVariable get variable => _variable!;
@override
InternalCatchVariable build(SourceLibraryBuilder library) {
if (_variable == null) {
bool isTypeOmitted = type is OmittedTypeBuilder;
DartType? builtType = type.build(library, TypeUse.parameterType);
String variableName = _wildcardIndex != null
? createWildcardFormalParameterName(_wildcardIndex)
: name;
_variable = intern.createCatchVariable(
name: variableName,
type: isTypeOmitted ? const DynamicType() : builtType,
isWildcard: isWildcard,
isImplicitlyTyped: isTypeOmitted,
fileOffset: fileOffset,
isFinal: modifiers.isFinal,
);
}
return _variable!;
}
@override
// Coverage-ignore(suite): Not run.
void onInferredType(DartType type) {
if (_variable != null) {
_variable!.type = type;
}
}
@override
String toString() => '$runtimeType($name)';
}
/// A builder for a formal parameter, i.e. a parameter on a method or
/// constructor.
class FormalParameterBuilder extends NamedBuilderImpl
with LookupResultMixin
implements ParameterVariableBuilder, InferredTypeListener {
static const String noNameSentinel = 'no name sentinel';
@override
final int fileOffset;
List<MetadataBuilder>? _metadata;
final Modifiers modifiers;
@override
TypeBuilder type;
@override
final String name;
/// If this parameter is a private named parameter that refers to an instance
/// field, then this is the corresponding public name for the parameter.
///
/// For example:
///
/// class C {
/// int? _x;
/// C({this._x});
/// }
///
/// Here, [publicName] for `_x` will be `x`. If the formal parameter isn't a
/// private named parameter that refers to a field, then this is `null`.
final String? publicName;
@override
final Uri fileUri;
@override
final FormalParameterKind kind;
/// The variable declaration created for this formal parameter.
InternalFunctionParameter? _variable;
/// The first token of the default value, if any.
///
/// This is stored until outlines have been built through
/// [buildOutlineExpressions].
Token? _defaultValueToken;
bool defaultValueWasInferred = false;
/// True if the default value was declared by the programmer.
final bool hasImmediatelyDeclaredDefaultValue;
/// True if the initializer was declared by the programmer, either directly
/// or inferred from a super parameter.
bool hasDeclaredDefaultValue;
final bool isExtensionThis;
/// If this is a wildcard variable, this holds the index used to create a
/// uniquely named kernel variable for it.
final int? _wildcardIndex;
final int? nameOffset;
@override
final bool isPrimaryConstructorParameter;
new({
required this.kind,
this._metadata,
required this.modifiers,
required this.type,
required this.name,
required this.fileOffset,
required this.fileUri,
this.isExtensionThis = false,
Token? defaultValueToken,
required this.hasImmediatelyDeclaredDefaultValue,
int? wildcardIndex,
this.publicName,
required this.nameOffset,
this.isPrimaryConstructorParameter = false,
InternalFunctionParameter? variable,
}) : this.hasDeclaredDefaultValue = hasImmediatelyDeclaredDefaultValue,
this._defaultValueToken = defaultValueToken,
this._wildcardIndex = wildcardIndex,
this._variable = variable {
type.registerInferredTypeListener(this);
}
@override
// Coverage-ignore(suite): Not run.
String get fullNameForErrors => name;
/// Returns and removes the metadata from this builder.
///
/// Metadata builders hold tokens, and since metadata is generally not
/// processed from the builder, but instead from the expressions during body
/// builder, the responsibility of handling the metadata must be passed on.
List<MetadataBuilder>? takeMetadata() {
List<MetadataBuilder>? result = _metadata;
_metadata = null;
return result;
}
@override
NamedBuilder get getable => this;
// An initializing formal parameter might be final without its
// [Variable] being final. See
// [ProcedureBuilder.computeFormalParameterInitializerScope]..
@override
bool get isAssignable =>
variable.isAssignable &&
!isInitializingFormal &&
!isSuperInitializingFormal;
@override
bool get isConst => modifiers.isConst;
bool get isCovariantByDeclaration => modifiers.isCovariant;
@override
bool get isFinal => variable.isFinal;
bool get isInitializingFormal => modifiers.isInitializingFormal;
@override
bool get isLate => variable.isLate;
bool get isSuperInitializingFormal => modifiers.isSuperInitializingFormal;
@override
bool get isWildcard => _wildcardIndex != null;
@override
// Coverage-ignore(suite): Not run.
Builder? get parent => null;
@override
NamedBuilder? get setable => isAssignable ? this : null;
@override
InternalFunctionParameter get variable => _variable!;
@override
InternalFunctionParameter build(SourceLibraryBuilder library) {
if (_variable == null) {
bool isTypeOmitted = type is OmittedTypeBuilder;
DartType? builtType = type.build(library, TypeUse.parameterType);
String? variableName = switch (name) {
noNameSentinel => null,
// If the parameter is a private named parameter, use the public name
// for the corresponding variable.
_ when publicName != null => publicName,
_ when _wildcardIndex != null => createWildcardFormalParameterName(
_wildcardIndex,
),
_ => name,
};
switch (kind) {
case FormalParameterKind.requiredPositional:
case FormalParameterKind.optionalPositional:
_variable = intern.createPositionalParameter(
cosmeticName: variableName,
type: isTypeOmitted ? const DynamicType() : builtType,
defaultValue: null,
isCovariantByDeclaration: isCovariantByDeclaration,
isInitializingFormal: isInitializingFormal,
isSuperInitializingFormal: isSuperInitializingFormal,
isFinal: modifiers.isFinal,
hasDeclaredDefaultValue: hasDeclaredDefaultValue,
isLowered: isExtensionThis,
isSynthesized: name == noNameSentinel,
isWildcard: isWildcard,
fileOffset: fileOffset,
isImplicitlyTyped: isTypeOmitted,
);
case FormalParameterKind.requiredNamed:
case FormalParameterKind.optionalNamed:
_variable = intern.createNamedParameter(
parameterName: variableName!,
type: isTypeOmitted ? const DynamicType() : builtType,
defaultValue: null,
isCovariantByDeclaration: isCovariantByDeclaration,
isRequired: isRequiredNamed,
isInitializingFormal: isInitializingFormal,
isSuperInitializingFormal: isSuperInitializingFormal,
isFinal: modifiers.isFinal,
hasDeclaredDefaultValue: hasDeclaredDefaultValue,
isSynthesized: name == noNameSentinel,
isWildcard: isWildcard,
isRenamedPrivateNamedParameter: publicName != null,
isImplicitlyTyped: isTypeOmitted,
fileOffset: fileOffset,
);
}
}
return _variable!;
}
/// Builds the default value from this [initializerToken] if this is a
/// formal parameter on a const constructor or instance method.
void buildOutlineExpressions({
required SourceLibraryBuilder libraryBuilder,
required DeclarationBuilder? declarationBuilder,
required SourceMemberBuilder memberBuilder,
required ExtensionScope extensionScope,
required LookupScope scope,
}) {
// Metadata is not processed through the builder, but instead from the
// expressions during body building, so we discard any metadata here.
takeMetadata();
// For const constructors we need to include default parameter values
// into the outline. For all other formals we need to call
// buildOutlineExpressions to clear defaultValueToken to prevent
// consuming too much memory.
Token? defaultValueToken = _takeDefaultValueToken();
if (_needsDefaultValuesBuiltAsOutlineExpressions(memberBuilder)) {
if (defaultValueToken != null) {
BodyBuilderContext bodyBuilderContext = new ParameterBodyBuilderContext(
libraryBuilder,
declarationBuilder,
this,
);
assert(!defaultValueWasInferred);
Resolver resolver = libraryBuilder.loader.createResolver();
Expression defaultValue = resolver.buildParameterDefaultValue(
libraryBuilder: libraryBuilder,
bodyBuilderContext: bodyBuilderContext,
extensionScope: extensionScope,
scope: scope,
fileUri: fileUri,
defaultValueToken: defaultValueToken,
declaredType: variable.type,
hasDeclaredDefaultValue: hasDeclaredDefaultValue,
);
variable.setInferredDefaultValue(defaultValue);
if (defaultValue is InvalidExpression) {
variable.hasErroneousDefaultValue = true;
}
} else if (kind.isOptional) {
// As done by BodyBuilder.endFormalParameter.
variable.setInferredDefaultValue(
extern.createNullLiteral(fileOffset: fileOffset),
);
}
defaultValueWasInferred = true;
}
}
/// Returns the [_defaultValueToken] field and without clearing it.
///
/// This is used to copy ownership of the token to the receiver, such that
/// both this [FormalParameterBuilder] and the receiver owns a copy. Tokens
/// need to be cleared during the outline phase to avoid holding the token
/// stream in memory.
///
/// This is used when creating primary constructor formal parameters, where
/// the default value should be used both to infer the field type and to
/// create the default value for the constructor parameter.
Token? copyDefaultValueToken() => _defaultValueToken;
void finalizeInitializingFormal(
DeclarationBuilder declarationBuilder,
SourceConstructorBuilder constructorBuilder,
ClassHierarchyBase hierarchy,
) {
String fieldName = isWildcardLoweredFormalParameter(name) ? '_' : name;
LookupResult? result = declarationBuilder.lookupLocalMember(fieldName);
Builder? fieldBuilder = result?.getable;
if (result is DuplicateMemberLookupResult) {
fieldBuilder = result.declarations.first;
}
if (fieldBuilder is SourcePropertyBuilder && fieldBuilder.hasField) {
DartType fieldType = fieldBuilder.inferFieldType(hierarchy);
fieldType = constructorBuilder.substituteFieldType(fieldType);
type.registerInferredType(fieldType);
} else {
type.registerInferredType(const DynamicType());
}
}
/// Creates the [FormalParameterBuilder] for a parameter used in the formal
/// parameters in a primary constructor.
///
/// If [isDeclaring] is `true` the parameter is marked as an initializing
/// formal whose type is inferred. Otherwise the parameter is marked as
/// final primary constructor parameter.
///
/// The created parameter replaces the original parameter in the primary
/// constructor declaration.
FormalParameterBuilder forPrimaryConstructor(
FragmentFactory builderFactory, {
required bool isDeclaring,
}) {
assert(_variable == null);
return new FormalParameterBuilder(
kind: kind,
modifiers: isDeclaring
? (modifiers | Modifiers.InitializingFormal)
: modifiers,
type: isDeclaring
? builderFactory.addInferableType(InferenceDefaultType.NullableObject)
: type,
name: name,
fileOffset: fileOffset,
nameOffset: nameOffset,
fileUri: fileUri,
isExtensionThis: isExtensionThis,
defaultValueToken: copyDefaultValueToken(),
hasImmediatelyDeclaredDefaultValue: hasImmediatelyDeclaredDefaultValue,
publicName: publicName,
wildcardIndex: _wildcardIndex,
isPrimaryConstructorParameter: !isDeclaring,
);
}
@override
void onInferredType(DartType type) {
_variable?.type = type;
}
@override
String toString() => '$runtimeType($name)';
/// Returns the [_defaultValueToken] field and clears it.
///
/// This is used to transfer ownership of the token to the receiver. Tokens
/// need to be cleared during the outline phase to avoid holding the token
/// stream in memory.
Token? _takeDefaultValueToken() {
Token? initializerToken = _defaultValueToken;
_defaultValueToken = null;
return initializerToken;
}
static bool _needsDefaultValuesBuiltAsOutlineExpressions(
SourceMemberBuilder memberBuilder,
) {
// For modular compilation we need to include default values for optional
// and named parameters in several cases:
// * for const constructors to enable constant evaluation,
// * for instance methods because these might be needed to generated
// noSuchMethod forwarders,
// * for generative constructors to support forwarding constructors
// in mixin applications, and
// * for factories, to uphold the invariant that optional parameters always
// have default values, even during modular compilation.
if (memberBuilder is SourceConstructorBuilder) {
return true;
} else if (memberBuilder is SourceFactoryBuilder) {
return true;
} else {
return memberBuilder.isClassInstanceMember;
}
}
}
class FunctionTypeParameterBuilder implements ParameterBuilder {
@override
final FormalParameterKind kind;
@override
final TypeBuilder type;
@override
final String? name;
new(this.kind, this.type, this.name);
@override
int get fileOffset {
throw new UnsupportedError("${this.runtimeType}.fileOffset");
}
@override
// Coverage-ignore(suite): Not run.
bool get isWildcard => false;
@override
InternalVariable build(SourceLibraryBuilder library) {
throw new UnsupportedError("${this.runtimeType}.build");
}
}
abstract class ParameterBuilder {
int get fileOffset;
/// Whether this formal parameter is a wildcard variable.
bool get isWildcard;
/// The kind of this parameter, i.e. if it's required, positional optional,
/// or named optional.
FormalParameterKind get kind;
String? get name;
TypeBuilder get type;
InternalVariable build(SourceLibraryBuilder library);
}
abstract class ParameterVariableBuilder
implements ParameterBuilder, VariableBuilder {}
extension ParameterBuilderExtension on ParameterBuilder {
bool get isNamed => kind.isNamed;
bool get isOptional => kind.isOptional;
// TODO(johnniwinther): This was previously named `isOptional` so we might
// have some uses that intended to use the now existing `isOptional` method.
bool get isOptionalPositional => !isRequiredPositional;
bool get isPositional => kind.isPositional;
bool get isRequiredNamed => kind.isRequiredNamed;
bool get isRequiredPositional => kind.isRequiredPositional;
}