Files
sdk/pkg/dart2wasm/lib/param_info.dart
T
Martin Kustermann 7882084ab5 [dart2wasm] Correctly deal with abstract members in selector building
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>
2025-05-19 06:46:18 -07:00

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;
}
}
}
}
}