7882084ab5
When we create a `Selector` we compute various information based on all members belonging to that selector. Before this CL this was split up: Some things like `ParameterInfo` was calculated&updated while discovering members, others was calculated after all members were discovered. We now make all fields whose value depends on knowing all members `late final` and initialize those fields after having found all members of a selector. When we compute the `ParameterInfo` of the selector we take special care: If there's non-abstract implementations and the selector is not overridable by dynamic modules then we create the `ParameterInfo` by looking only at the non-abstract members => This increases precision as we ignore the types and default values of optional parameters for abstract members If there's no implementations of the selector it may still be referred to by instance invocation AST nodes (which are unreachable). The current code generator still needs a `Selector.signature` in order to evaluate arguments for such nodes. => We calculate `ParameterInfo` based on the abstract member and use `defaultSentinel` for optional parameters. If a selector can have implementations in dynamic modules then we cannot determine whether all implementations have the same default value of optional parameters. => We calculate `ParameterInfo` using `defaultSentinel` value for all optional parameters. Ensuring the caller doesn't pass them but the callee will default to it's default value if it wasn't passed by caller. See added test case for when we (before this CL) may use unreachable members in the param info / signature calculation and how that could lead to a compiler bug (before this CL) due to a missing `VariableDeclaration.initializer` TEST=web/wasm/unreachable_selector_implementation_test Change-Id: I3628177f8e93c5d24ff0eb0ff8fee6121a4a08d1 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/428541 Reviewed-by: Nate Biggs <natebiggs@google.com> Commit-Queue: Martin Kustermann <kustermann@google.com>
147 lines
5.1 KiB
Dart
147 lines
5.1 KiB
Dart
// 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(VariableDeclaration param) {
|
|
Expression? initializer = param.initializer;
|
|
if (initializer is ConstantExpression) {
|
|
return initializer.constant;
|
|
} else if (initializer == null) {
|
|
return null;
|
|
} else {
|
|
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])!;
|
|
});
|
|
|
|
final named = {
|
|
for (VariableDeclaration param in function.namedParameters)
|
|
if (param.isRequired)
|
|
param.name!: null
|
|
else
|
|
param.name!: useDefaultValueSentinel
|
|
? defaultValueSentinel
|
|
: _defaultValue(param)!,
|
|
};
|
|
|
|
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 (VariableDeclaration param in function.namedParameters)
|
|
param.name!: _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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|