| // 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:front_end/src/codes/diagnostic.dart' as diag; | |
| import 'package:kernel/ast.dart'; | |
| import 'package:kernel/clone.dart' show CloneVisitorNotMembers; | |
| import 'package:kernel/type_algebra.dart' show Substitution; | |
| import 'package:kernel/type_environment.dart'; | |
| import '../type_inference/context_allocation_strategy.dart'; | |
| import '../builder/library_builder.dart'; | |
| import '../source/stack_listener_impl.dart' show AsyncModifier; | |
| import 'external_ast_helper.dart' as extern; | |
| /// Data for clone default values for synthesized function nodes once the | |
| /// original default values have been computed. | |
| /// | |
| /// This is used for constructors in unnamed mixin application, which are | |
| /// created from the constructors in the superclass, and for tear off lowerings | |
| /// for redirecting factories, which are created from the effective target | |
| /// constructor. | |
| class DelayedDefaultValueCloner<T extends TreeNode> { | |
| /// The original constructor or procedure. | |
| final Member _source; | |
| /// The synthesized constructor or procedure. | |
| final T _target; | |
| final ParameterMapping _parameterMapping; | |
| bool isOutlineNode; | |
| final LibraryBuilder _libraryBuilder; | |
| CloneVisitorNotMembers? _cloner; | |
| /// Set to `true` we default values have been cloned, ensuring that cloning | |
| /// isn't performed twice. | |
| bool _hasCloned = false; | |
| new({ | |
| required this._source, | |
| required this._target, | |
| required this._parameterMapping, | |
| this.isOutlineNode = false, | |
| required LibraryBuilder libraryBuilder, | |
| }) : _libraryBuilder = libraryBuilder; | |
| /// The original constructor or procedure. | |
| TreeNode get source => _source; | |
| /// The synthesized constructor or procedure. | |
| TreeNode get target => _target; | |
| void cloneDefaultValues(TypeEnvironment typeEnvironment) { | |
| if (_hasCloned) return; | |
| // TODO(ahe): It is unclear if it is legal to use type parameters in | |
| // default values, but Fasta is currently allowing it, and the VM | |
| // accepts it. If it isn't legal, the we can speed this up by using a | |
| // single cloner without substitution. | |
| // For mixin application constructors, the argument count is the same, but | |
| // for redirecting tear off lowerings, the argument count of the tear off | |
| // can be less than that of the redirection target or, in errors cases, be | |
| // unrelated. | |
| FunctionNode sourceFunctionNode = _source.function!; | |
| FunctionNode targetFunctionNode = _parameterMapping.getTargetFunctionNode( | |
| _target, | |
| ); | |
| NamedParameterCache namedParameterCache = new NamedParameterCache( | |
| sourceFunctionNode, | |
| ); | |
| Uri? targetFileUri = _parameterMapping.getTargetFileUriForCheckingTypes( | |
| target, | |
| ); | |
| for ( | |
| int targetIndex = targetFunctionNode.requiredParameterCount; | |
| targetIndex < targetFunctionNode.positionalParameters.length; | |
| targetIndex++ | |
| ) { | |
| PositionalParameter targetParameter = | |
| targetFunctionNode.positionalParameters[targetIndex]; | |
| PositionalParameter? sourceParameter = _parameterMapping | |
| .getPositionalSourceParameter( | |
| source: sourceFunctionNode, | |
| target: targetFunctionNode, | |
| targetIndex: targetIndex, | |
| ); | |
| if (sourceParameter != null) { | |
| if (targetFileUri != null) { | |
| _cloneDefaultValueForSuperParameters( | |
| sourceParameter, | |
| targetParameter, | |
| typeEnvironment, | |
| targetFileUri: targetFileUri, | |
| ); | |
| } else { | |
| _cloneDefaultValue(sourceParameter, targetParameter); | |
| } | |
| } else | |
| // TODO(johnniwinther): Why do we have an explicit `null` default value | |
| // on super parameters without a declared defaultValue? | |
| if (!targetParameter.hasDeclaredDefaultValue) { | |
| // Error case: use `null` as default value. | |
| targetParameter.defaultValue = extern.createNullLiteral( | |
| fileOffset: TreeNode.noOffset, | |
| )..parent = targetParameter; | |
| if (targetParameter.type.nullability != Nullability.nullable) { | |
| // Coverage-ignore-block(suite): Not run. | |
| targetParameter.hasErroneousDefaultValue = true; | |
| } | |
| } | |
| } | |
| for ( | |
| int targetIndex = 0; | |
| targetIndex < targetFunctionNode.namedParameters.length; | |
| targetIndex++ | |
| ) { | |
| NamedParameter targetParameter = | |
| targetFunctionNode.namedParameters[targetIndex]; | |
| if (targetParameter.isRequired) continue; | |
| NamedParameter? sourceParameter = _parameterMapping | |
| .getNamedSourceParameter( | |
| source: sourceFunctionNode, | |
| target: targetFunctionNode, | |
| targetIndex: targetIndex, | |
| namedParameterCache: namedParameterCache, | |
| ); | |
| if (sourceParameter != null) { | |
| if (targetFileUri != null) { | |
| _cloneDefaultValueForSuperParameters( | |
| sourceParameter, | |
| targetParameter, | |
| typeEnvironment, | |
| targetFileUri: targetFileUri, | |
| ); | |
| } else { | |
| _cloneDefaultValue(sourceParameter, targetParameter); | |
| } | |
| } else | |
| // TODO(johnniwinther): Why do we have an explicit `null` default value | |
| // on super parameters without a declared default value? | |
| if (!targetParameter.hasDeclaredDefaultValue) { | |
| // Error case: use `null` as default value. | |
| targetParameter.defaultValue = extern.createNullLiteral( | |
| fileOffset: TreeNode.noOffset, | |
| )..parent = targetParameter; | |
| if (targetParameter.type.nullability != Nullability.nullable) { | |
| targetParameter.hasErroneousDefaultValue = true; | |
| } | |
| } | |
| } | |
| _hasCloned = true; | |
| return; | |
| } | |
| void _cloneDefaultValue( | |
| FunctionParameter originalParameter, | |
| FunctionParameter clonedParameter, | |
| ) { | |
| if (originalParameter.defaultValue != null) { | |
| CloneVisitorNotMembers cloner = _cloner ??= new CloneVisitorNotMembers(); | |
| clonedParameter.defaultValue = cloner.clone( | |
| originalParameter.defaultValue!, | |
| )..parent = clonedParameter; | |
| } | |
| clonedParameter.hasErroneousDefaultValue |= | |
| originalParameter.hasErroneousDefaultValue; | |
| } | |
| void _cloneDefaultValueForSuperParameters( | |
| FunctionParameter sourceParameter, | |
| FunctionParameter targetParameter, | |
| TypeEnvironment typeEnvironment, { | |
| required Uri targetFileUri, | |
| }) { | |
| Expression? sourceDefaultValue = sourceParameter.defaultValue; | |
| DartType? sourceDefaultValueType = sourceDefaultValue?.getStaticType( | |
| new StaticTypeContext(_source, typeEnvironment), | |
| ); | |
| DartType targetParameterType = targetParameter.type; | |
| if (sourceDefaultValueType != null && | |
| typeEnvironment.isSubtypeOf( | |
| sourceDefaultValueType, | |
| targetParameterType, | |
| )) { | |
| _cloneDefaultValue(sourceParameter, targetParameter); | |
| } else if (sourceDefaultValue == null) { | |
| targetParameter.defaultValue = extern.createNullLiteral( | |
| fileOffset: TreeNode.noOffset, | |
| )..parent = targetParameter; | |
| } else { | |
| targetParameter.hasDeclaredDefaultValue = false; | |
| if (targetParameterType.isPotentiallyNonNullable) { | |
| _libraryBuilder.addProblem( | |
| diag.optionalSuperParameterWithoutInitializer.withArguments( | |
| superParameterType: targetParameter.type, | |
| superParameterName: targetParameter.parameterName, | |
| ), | |
| targetParameter.fileOffset, | |
| targetParameter.parameterName.length, | |
| targetFileUri, | |
| ); | |
| targetParameter.hasErroneousDefaultValue = true; | |
| } | |
| } | |
| } | |
| @override | |
| String toString() { | |
| return "DelayedDefaultValueCloner(original=${_source}, " | |
| "synthesized=${_target})"; | |
| } | |
| } | |
| class NamedParameterCache(final FunctionNode _function) { | |
| late final Map<String, NamedParameter> _map = _computeMap(); | |
| Map<String, NamedParameter> _computeMap() { | |
| Map<String, NamedParameter> map = {}; | |
| for (NamedParameter parameter in _function.namedParameters) { | |
| map[parameter.parameterName] ??= parameter; | |
| } | |
| return map; | |
| } | |
| NamedParameter? operator [](String name) => _map[name]; | |
| } | |
| /// Interface used by [DelayedDefaultValueCloner] to map target parameters to | |
| /// source parameters from which default values should be cloned. | |
| abstract class ParameterMapping<T extends TreeNode> { | |
| /// Returns the [FunctionNode] for the [target] which holds the target | |
| /// parameters. | |
| FunctionNode getTargetFunctionNode(T target); | |
| /// Returns the [Uri] for the [target] if needed for checking the type of the | |
| /// cloned default value against the target parameter type. If no check is | |
| /// needed, `null` is returned. | |
| /// | |
| /// The check is needed for super parameters where the super parameter can be | |
| /// declared with a type that doesn't support the default value. For instance | |
| /// | |
| /// class Super { | |
| /// new({num a = 3.14}); | |
| /// } | |
| /// class Class { | |
| /// new({int super.a}); | |
| /// } | |
| /// | |
| /// where the default value `3.14` is not valid for a parameter of type `int`. | |
| Uri? getTargetFileUriForCheckingTypes(T target); | |
| /// Returns the [PositionalParameter] from [source] from which the default | |
| /// value for the [targetIndex]th positional parameter from [target] should be | |
| /// cloned, if any. | |
| PositionalParameter? getPositionalSourceParameter({ | |
| required FunctionNode source, | |
| required FunctionNode target, | |
| required int targetIndex, | |
| }); | |
| /// Returns the [NamedParameter] from [source] from which the default value | |
| /// for the [targetIndex]th named parameter from [target] should be cloned, if | |
| /// any. | |
| /// | |
| /// [namedParameterCache] can be used to lookup named parameters in [source] | |
| /// by name. | |
| NamedParameter? getNamedSourceParameter({ | |
| required FunctionNode source, | |
| required FunctionNode target, | |
| required int targetIndex, | |
| required NamedParameterCache namedParameterCache, | |
| }); | |
| } | |
| class SuperParameterMapping( | |
| final Map<int, int> positionalTargetToSourceMapping, | |
| final Map<int, String> namedTargetToSourceMapping, | |
| ) implements ParameterMapping<Constructor> { | |
| @override | |
| FunctionNode getTargetFunctionNode(Constructor target) => target.function; | |
| @override | |
| Uri? getTargetFileUriForCheckingTypes(Constructor target) => target.fileUri; | |
| @override | |
| NamedParameter? getNamedSourceParameter({ | |
| required FunctionNode source, | |
| required FunctionNode target, | |
| required int targetIndex, | |
| required NamedParameterCache namedParameterCache, | |
| }) { | |
| String? sourceName = namedTargetToSourceMapping[targetIndex]; | |
| return sourceName != null ? namedParameterCache[sourceName] : null; | |
| } | |
| @override | |
| PositionalParameter? getPositionalSourceParameter({ | |
| required FunctionNode source, | |
| required FunctionNode target, | |
| required int targetIndex, | |
| }) { | |
| int? sourceIndex = positionalTargetToSourceMapping[targetIndex]; | |
| return sourceIndex != null | |
| ? source.positionalParameters[sourceIndex] | |
| : null; | |
| } | |
| } | |
| class const DirectParameterMapping() implements ParameterMapping<Member> { | |
| @override | |
| FunctionNode getTargetFunctionNode(Member target) => target.function!; | |
| @override | |
| Uri? getTargetFileUriForCheckingTypes(Member target) => null; | |
| @override | |
| NamedParameter? getNamedSourceParameter({ | |
| required FunctionNode source, | |
| required FunctionNode target, | |
| required int targetIndex, | |
| required NamedParameterCache namedParameterCache, | |
| }) { | |
| NamedParameter? sourceParameter = | |
| namedParameterCache[target.namedParameters[targetIndex].parameterName]; | |
| return sourceParameter != null && !sourceParameter.isRequired | |
| ? sourceParameter | |
| : null; | |
| } | |
| @override | |
| PositionalParameter? getPositionalSourceParameter({ | |
| required FunctionNode source, | |
| required FunctionNode target, | |
| required int targetIndex, | |
| }) { | |
| return targetIndex >= source.requiredParameterCount && | |
| targetIndex < source.positionalParameters.length | |
| ? source.positionalParameters[targetIndex] | |
| : null; | |
| } | |
| } | |
| class TypeDependency { | |
| final Member synthesized; | |
| final Member original; | |
| final Substitution substitution; | |
| final bool copyReturnType; | |
| bool _hasBeenInferred = false; | |
| new( | |
| this.synthesized, | |
| this.original, | |
| this.substitution, { | |
| required this.copyReturnType, | |
| }); | |
| void copyInferred() { | |
| if (_hasBeenInferred) return; | |
| for (int i = 0; i < original.function!.positionalParameters.length; i++) { | |
| PositionalParameter synthesizedParameter = | |
| synthesized.function!.positionalParameters[i]; | |
| PositionalParameter originalParameter = | |
| original.function!.positionalParameters[i]; | |
| synthesizedParameter.type = substitution.substituteType( | |
| originalParameter.type, | |
| ); | |
| if (!synthesizedParameter.hasDeclaredDefaultValue) { | |
| synthesizedParameter.hasDeclaredDefaultValue = | |
| originalParameter.hasDeclaredDefaultValue; | |
| } | |
| } | |
| for (int i = 0; i < original.function!.namedParameters.length; i++) { | |
| NamedParameter synthesizedParameter = | |
| synthesized.function!.namedParameters[i]; | |
| NamedParameter originalParameter = original.function!.namedParameters[i]; | |
| synthesizedParameter.type = substitution.substituteType( | |
| originalParameter.type, | |
| ); | |
| if (!synthesizedParameter.hasDeclaredDefaultValue) { | |
| synthesizedParameter.hasDeclaredDefaultValue = | |
| originalParameter.hasDeclaredDefaultValue; | |
| } | |
| } | |
| if (copyReturnType) { | |
| synthesized.function!.returnType = substitution.substituteType( | |
| original.function!.returnType, | |
| ); | |
| } | |
| _hasBeenInferred = true; | |
| } | |
| } | |
| // Coverage-ignore(suite): Not run. | |
| /// Copies properties, function parameters and body from the [augmentation] | |
| /// procedure to its [origin]. | |
| void finishProcedureAugmentation(Procedure origin, Procedure augmentation) { | |
| origin.fileUri = augmentation.fileUri; | |
| origin.fileStartOffset = augmentation.fileStartOffset; | |
| origin.fileOffset = augmentation.fileOffset; | |
| origin.fileEndOffset = augmentation.fileEndOffset; | |
| origin.isAbstract = augmentation.isAbstract; | |
| origin.isExternal = augmentation.isExternal; | |
| origin.function = augmentation.function; | |
| origin.function.parent = origin; | |
| } | |
| extension FunctionNodeExtension on FunctionNode { | |
| void registerFunctionBody( | |
| Statement body, { | |
| AsyncModifier asyncModifier = AsyncModifier.implicitSync, | |
| DartType? emittedValueType = null, | |
| }) { | |
| assert( | |
| !(asyncModifier.kind == AsyncMarker.Sync && emittedValueType != null), | |
| "Unexpected emitted value type for sync function.", | |
| ); | |
| assert( | |
| !(asyncModifier.kind != AsyncMarker.Sync && emittedValueType == null), | |
| "Missing emitted value type for non-sync function.", | |
| ); | |
| this.body = body..parent = this; | |
| this.asyncMarker = asyncModifier.kind; | |
| this.dartAsyncMarker = asyncModifier.kind; | |
| this.emittedValueType = emittedValueType; | |
| } | |
| void registerScopeProviderInfo(ScopeProviderInfo? scopeProviderInfo) { | |
| if (scopeProviderInfo != null) { | |
| this.thisVariable = scopeProviderInfo.thisVariable?..parent = this; | |
| this.scope = scopeProviderInfo.scope; | |
| } | |
| } | |
| } |