| // Copyright (c) 2022, 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:kernel/ast.dart'; |
| |
| import 'reference_extensions.dart'; |
| |
| /// Information about optional parameters and their default values for a member |
| /// or a set of members belonging to the same override group. |
| class ParameterInfo { |
| final int typeParamCount; |
| |
| /// Default values of optional positonal parameters. `positional[i] == null` |
| /// means positional parameter `i` is not optional. |
| final List<Constant?> positional; |
| |
| /// Default values of named parameters. Similar to [positional], `null` means |
| /// the parameter is not optional. |
| final Map<String, Constant?> named; |
| |
| final bool takesContextOrReceiver; |
| |
| // Dispatch table builder updates `ParameterInfo`s, do not access late fields |
| // until the `ParameterInfo` is complete. |
| late final List<String> names = named.keys.toList()..sort(); |
| |
| late final Map<String, int> nameIndex = { |
| for (int i = 0; i < names.length; i++) names[i]: positional.length + i, |
| }; |
| |
| /// A special marker value to use for default parameter values to indicate |
| /// that different implementations within the same selector have different |
| /// default values. |
| static final Constant defaultValueSentinel = UnevaluatedConstant( |
| InvalidExpression("Default value sentinel"), |
| ); |
| |
| int get paramCount => positional.length + named.length; |
| |
| static Constant? defaultValue(FunctionParameter param, [Member? member]) { |
| Expression? defaultValue = param.defaultValue; |
| if (defaultValue is ConstantExpression) { |
| return defaultValue.constant; |
| } |
| |
| if (defaultValue == null) { |
| if (member is Procedure && member.isNoSuchMethodForwarder) { |
| // Optional parameters may not be supplied by the caller (they are |
| // optional) but the CFE-inserted no-such-method-forwarders may not |
| // have default values (even for non-nullable parameters). In that case |
| // we use `null` as the default value. The `noSuchMethod` will then be |
| // given an `Invocation` object with `null` for the parameter. |
| // |
| // See |
| // * https://github.com/dart-lang/language/issues/3331 |
| // * https://github.com/dart-lang/sdk/issues/63958 |
| return NullConstant(); |
| } |
| return null; |
| } |
| |
| throw "Non-constant default value"; |
| } |
| |
| ParameterInfo._( |
| this.takesContextOrReceiver, |
| this.typeParamCount, |
| this.positional, |
| this.named, |
| ); |
| |
| factory ParameterInfo.fromMember( |
| Reference target, |
| bool useDefaultValueSentinel, |
| ) { |
| final member = target.asMember; // Constructor, Field, or Procedure |
| assert(!member.isAbstract || useDefaultValueSentinel); |
| final function = member.function; |
| |
| if (target.isTearOffReference) { |
| // Tear-off getters don't take type parameters even if the member is |
| // generic. |
| return ParameterInfo._(true, 0, [], {}); |
| } |
| |
| if (function != null) { |
| // Constructor, or static or instance method. |
| assert(member is Constructor || member is Procedure); |
| |
| final typeParamCount = |
| (member is Constructor |
| ? member.enclosingClass.typeParameters |
| : function.typeParameters) |
| .length; |
| |
| final positional = List.generate(function.positionalParameters.length, ( |
| i, |
| ) { |
| // A required parameter has no default value. |
| if (i < function.requiredParameterCount) return null; |
| if (useDefaultValueSentinel) return defaultValueSentinel; |
| return defaultValue(function.positionalParameters[i], member)!; |
| }); |
| |
| final named = { |
| for (NamedParameter param in function.namedParameters) |
| if (param.isRequired) |
| param.parameterName: null |
| else |
| param.parameterName: useDefaultValueSentinel |
| ? defaultValueSentinel |
| : defaultValue(param, member)!, |
| }; |
| |
| return ParameterInfo._( |
| member.isInstanceMember, |
| typeParamCount, |
| positional, |
| named, |
| ); |
| } |
| |
| // A setter or getter. A setter parameter has no default value. |
| assert(member is Field); |
| return ParameterInfo._(true, 0, [if (target.isSetter) null], {}); |
| } |
| |
| factory ParameterInfo.fromLocalFunction(FunctionNode function) { |
| final typeParamCount = function.typeParameters.length; |
| final positional = List.generate(function.positionalParameters.length, (i) { |
| // A required parameter has no default value. |
| if (i < function.requiredParameterCount) return null; |
| return defaultValue(function.positionalParameters[i]); |
| }); |
| final named = { |
| for (NamedParameter param in function.namedParameters) |
| param.parameterName: param.isRequired ? null : defaultValue(param), |
| }; |
| return ParameterInfo._(true, typeParamCount, positional, named); |
| } |
| |
| void merge(ParameterInfo other) { |
| assert(typeParamCount == other.typeParamCount); |
| assert(takesContextOrReceiver == other.takesContextOrReceiver); |
| for (int i = 0; i < other.positional.length; i++) { |
| if (i >= positional.length) { |
| positional.add(other.positional[i]); |
| } else { |
| if (positional[i] == null) { |
| positional[i] = other.positional[i]; |
| } else if (other.positional[i] != null) { |
| if (positional[i] != other.positional[i]) { |
| // Default value differs between implementations. |
| positional[i] = defaultValueSentinel; |
| } |
| } |
| } |
| } |
| for (String name in other.named.keys) { |
| Constant? value = named[name]; |
| Constant? otherValue = other.named[name]; |
| if (value == null) { |
| named[name] = otherValue; |
| } else if (otherValue != null) { |
| if (value != otherValue) { |
| // Default value differs between implementations. |
| named[name] = defaultValueSentinel; |
| } |
| } |
| } |
| } |
| } |