mirror of
https://github.com/dart-lang/sdk
synced 2024-10-14 09:22:12 +00:00
Front end: initial implementation of type inference for object patterns.
This code handles simple cases. Still TBD: - Object patterns that refer to typedefs - Object patterns that refer to classes with f-bounded polymorphism - Inference of `dynamic` when there is no bound Bug: https://github.com/dart-lang/sdk/issues/51795 Change-Id: I00acae6ba111f7b170650cfeffbfd2aaf7f71e42 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/290347 Reviewed-by: Chloe Stefantsova <cstefantsova@google.com> Commit-Queue: Paul Berry <paulberry@google.com>
This commit is contained in:
parent
4caae04fc8
commit
6b4878b55f
|
@ -9170,10 +9170,13 @@ class BodyBuilder extends StackListenerImpl
|
|||
push(typeArguments ?? NullValues.TypeArguments);
|
||||
handleType(firstIdentifier, null);
|
||||
TypeBuilder typeBuilder = pop() as TypeBuilder;
|
||||
TypeDeclarationBuilder? typeDeclaration = typeBuilder.declaration;
|
||||
DartType type = buildDartType(typeBuilder, TypeUse.objectPatternType,
|
||||
allowPotentiallyConstantType: true);
|
||||
push(forest.createObjectPattern(
|
||||
firstIdentifier.charOffset, type, fields ?? <NamedPattern>[]));
|
||||
push(new ObjectPatternInternal(type, fields ?? <NamedPattern>[],
|
||||
typeDeclaration is TypeAliasBuilder ? typeDeclaration.typedef : null,
|
||||
hasExplicitTypeArguments: typeArguments != null)
|
||||
..fileOffset = firstIdentifier.charOffset);
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -1038,11 +1038,6 @@ class Forest {
|
|||
return new NullCheckPattern(pattern)..fileOffset = fileOffset;
|
||||
}
|
||||
|
||||
ObjectPattern createObjectPattern(
|
||||
int fileOffset, DartType type, List<NamedPattern> fields) {
|
||||
return new ObjectPattern(type, fields)..fileOffset = fileOffset;
|
||||
}
|
||||
|
||||
OrPattern createOrPattern(int fileOffset, Pattern left, Pattern right,
|
||||
{required List<VariableDeclaration> orPatternJointVariables}) {
|
||||
return new OrPattern(left, right,
|
||||
|
|
|
@ -3682,3 +3682,19 @@ class PatternForMapEntry extends TreeNode
|
|||
return "PatternForMapEntry(${toStringInternal()})";
|
||||
}
|
||||
}
|
||||
|
||||
/// Data structure used by the body builder in place of [ObjectPattern], to
|
||||
/// allow additional information to be captured that is needed during type
|
||||
/// inference.
|
||||
class ObjectPatternInternal extends ObjectPattern {
|
||||
/// If the type name in the object pattern refers to a typedef, the typedef in
|
||||
/// question; otherwise `null`.
|
||||
final Typedef? typedef;
|
||||
|
||||
/// Indicates whether the object pattern included explicit type arguments; if
|
||||
/// `true` this means that no further type inference needs to be performed.
|
||||
final bool hasExplicitTypeArguments;
|
||||
|
||||
ObjectPatternInternal(super.requiredType, super.fields, this.typedef,
|
||||
{required this.hasExplicitTypeArguments});
|
||||
}
|
||||
|
|
|
@ -10251,6 +10251,8 @@ class InferenceVisitorImpl extends InferenceVisitorBase
|
|||
ValueKinds.Pattern, node.fields.length)
|
||||
]));
|
||||
|
||||
node.requiredType = analysisResult.requiredType;
|
||||
|
||||
Pattern? replacement;
|
||||
|
||||
InvalidExpression? error =
|
||||
|
@ -10873,15 +10875,65 @@ class InferenceVisitorImpl extends InferenceVisitorBase
|
|||
return new RecordType(positional, namedFields, Nullability.nonNullable);
|
||||
}
|
||||
|
||||
/// Infers type arguments corresponding to [typeParameters] so that, when
|
||||
/// substituted into [declaredType], the resulting type matches [contextType].
|
||||
List<DartType> _inferTypeArguments(
|
||||
{required List<TypeParameter> typeParameters,
|
||||
required DartType declaredType,
|
||||
required DartType contextType}) {
|
||||
TypeConstraintGatherer gatherer = typeSchemaEnvironment
|
||||
.setupGenericTypeInference(declaredType, typeParameters, contextType,
|
||||
isNonNullableByDefault: isNonNullableByDefault);
|
||||
List<DartType> inferredTypes = typeSchemaEnvironment.partialInfer(
|
||||
gatherer, typeParameters, null,
|
||||
isNonNullableByDefault: isNonNullableByDefault);
|
||||
// Type inference may not be able to infer a type for every type parameter;
|
||||
// if it can't fall back on the type parameter's bound.
|
||||
// TODO(paulberry): this doesn't work if the type is F-bounded because in
|
||||
// that case, the bound may refer to other type variables.
|
||||
for (int i = 0; i < inferredTypes.length; i++) {
|
||||
if (inferredTypes[i] is UnknownType) {
|
||||
DartType bound = typeParameters[i].bound;
|
||||
inferredTypes[i] = bound;
|
||||
}
|
||||
}
|
||||
return inferredTypes;
|
||||
}
|
||||
|
||||
@override
|
||||
DartType downwardInferObjectPatternRequiredType({
|
||||
required DartType matchedType,
|
||||
required Pattern pattern,
|
||||
required covariant ObjectPatternInternal pattern,
|
||||
}) {
|
||||
if (pattern is! ObjectPattern) return const InvalidType();
|
||||
// TODO(johnniwinther): Update this when language issue #2770 has been
|
||||
// resolved.
|
||||
return pattern.requiredType;
|
||||
DartType requiredType = pattern.requiredType;
|
||||
if (!pattern.hasExplicitTypeArguments) {
|
||||
if (pattern.typedef != null) {
|
||||
// TODO(paulberry): handle typedefs properly.
|
||||
}
|
||||
if (requiredType is InterfaceType &&
|
||||
requiredType.classNode.typeParameters.isNotEmpty) {
|
||||
List<TypeParameter> typeParameters =
|
||||
requiredType.classNode.typeParameters;
|
||||
|
||||
// It's possible that one of the callee type parameters might match a
|
||||
// type that already exists as part of inference. This might happen,
|
||||
// for instance, in the case where a method in a generic class contains
|
||||
// an object pattern naming the enclosing class. To avoid creating
|
||||
// invalid inference results, we need to create fresh type parameters.
|
||||
FreshTypeParameters fresh = getFreshTypeParameters(typeParameters);
|
||||
InterfaceType declaredType = new InterfaceType(requiredType.classNode,
|
||||
requiredType.declaredNullability, fresh.freshTypeArguments);
|
||||
typeParameters = fresh.freshTypeParameters;
|
||||
|
||||
List<DartType> inferredTypeArguments = _inferTypeArguments(
|
||||
typeParameters: typeParameters,
|
||||
declaredType: declaredType,
|
||||
contextType: matchedType);
|
||||
requiredType = new InterfaceType(requiredType.classNode,
|
||||
requiredType.declaredNullability, inferredTypeArguments);
|
||||
}
|
||||
}
|
||||
return requiredType;
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -64,7 +64,7 @@ static method test(dynamic value, core::bool expected) → void {
|
|||
}
|
||||
{
|
||||
final synthesized dynamic #4#0 = value;
|
||||
if(#4#0 is{ForNonNullableByDefault} self::Const<dynamic>) {
|
||||
if(#4#0 is{ForNonNullableByDefault} self::Const<core::Object?>) {
|
||||
matched = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,7 +64,7 @@ static method test(dynamic value, core::bool expected) → void {
|
|||
}
|
||||
{
|
||||
final synthesized dynamic #4#0 = value;
|
||||
if(#4#0 is{ForNonNullableByDefault} self::Const<dynamic>) {
|
||||
if(#4#0 is{ForNonNullableByDefault} self::Const<core::Object?>) {
|
||||
matched = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,7 +64,7 @@ static method test(dynamic value, core::bool expected) → void {
|
|||
}
|
||||
{
|
||||
final synthesized dynamic #4#0 = value;
|
||||
if(#4#0 is{ForNonNullableByDefault} self::Const<dynamic>) {
|
||||
if(#4#0 is{ForNonNullableByDefault} self::Const<core::Object?>) {
|
||||
matched = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,7 +64,7 @@ static method test(dynamic value, core::bool expected) → void {
|
|||
}
|
||||
{
|
||||
final synthesized dynamic #4#0 = value;
|
||||
if(#4#0 is{ForNonNullableByDefault} self::Const<dynamic>) {
|
||||
if(#4#0 is{ForNonNullableByDefault} self::Const<core::Object?>) {
|
||||
matched = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,7 +64,7 @@ static method test(dynamic value, core::bool expected) → void {
|
|||
}
|
||||
{
|
||||
final synthesized dynamic #4#0 = value;
|
||||
if(#4#0 is{ForNonNullableByDefault} self::Const<dynamic>) {
|
||||
if(#4#0 is{ForNonNullableByDefault} self::Const<core::Object?>) {
|
||||
matched = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -82,7 +82,7 @@ static method test(dynamic value, core::bool expected) → void {
|
|||
}
|
||||
}
|
||||
{
|
||||
if(#0#0 is{ForNonNullableByDefault} self::Const<dynamic>) {
|
||||
if(#0#0 is{ForNonNullableByDefault} self::Const<core::Object?>) {
|
||||
{
|
||||
matched = true;
|
||||
break #L1;
|
||||
|
|
|
@ -82,7 +82,7 @@ static method test(dynamic value, core::bool expected) → void {
|
|||
}
|
||||
}
|
||||
{
|
||||
if(#0#0 is{ForNonNullableByDefault} self::Const<dynamic>) {
|
||||
if(#0#0 is{ForNonNullableByDefault} self::Const<core::Object?>) {
|
||||
{
|
||||
matched = true;
|
||||
break #L1;
|
||||
|
|
|
@ -82,7 +82,7 @@ static method test(dynamic value, core::bool expected) → void {
|
|||
}
|
||||
}
|
||||
{
|
||||
if(#0#0 is{ForNonNullableByDefault} self::Const<dynamic>) {
|
||||
if(#0#0 is{ForNonNullableByDefault} self::Const<core::Object?>) {
|
||||
{
|
||||
matched = true;
|
||||
break #L1;
|
||||
|
|
|
@ -82,7 +82,7 @@ static method test(dynamic value, core::bool expected) → void {
|
|||
}
|
||||
}
|
||||
{
|
||||
if(#0#0 is{ForNonNullableByDefault} self::Const<dynamic>) {
|
||||
if(#0#0 is{ForNonNullableByDefault} self::Const<core::Object?>) {
|
||||
{
|
||||
matched = true;
|
||||
break #L1;
|
||||
|
|
|
@ -82,7 +82,7 @@ static method test(dynamic value, core::bool expected) → void {
|
|||
}
|
||||
}
|
||||
{
|
||||
if(#0#0 is{ForNonNullableByDefault} self::Const<dynamic>) {
|
||||
if(#0#0 is{ForNonNullableByDefault} self::Const<core::Object?>) {
|
||||
{
|
||||
matched = true;
|
||||
break #L1;
|
||||
|
|
109
tests/language/patterns/object_pattern_inference_test.dart
Normal file
109
tests/language/patterns/object_pattern_inference_test.dart
Normal file
|
@ -0,0 +1,109 @@
|
|||
// Copyright (c) 2023, 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.
|
||||
|
||||
// SharedOptions=--enable-experiment=patterns,records
|
||||
|
||||
// Test that the type arguments of object patterns are properly inferred.
|
||||
|
||||
import "package:expect/expect.dart";
|
||||
|
||||
import "../static_type_helper.dart";
|
||||
|
||||
sealed class B<T> {}
|
||||
|
||||
class C<T> extends B<T> {
|
||||
final T t;
|
||||
|
||||
// Note: we use this getter to obtain values for `expectStaticType` (rather
|
||||
// than a getter returning simply `T`) to ensure that an error is reported if
|
||||
// `T` gets inferred to be `dynamic`.
|
||||
List<T> get listOfT => [t];
|
||||
|
||||
C(this.t);
|
||||
}
|
||||
|
||||
bool explicitTypeArguments(B<int> b) {
|
||||
switch (b) {
|
||||
case C<num>(listOfT: var x):
|
||||
x.expectStaticType<Exactly<List<num>>>();
|
||||
// Since C<num> isn't a subtype of B<int>, `b` is not promoted.
|
||||
b.expectStaticType<Exactly<B<int>>>();
|
||||
return true;
|
||||
}
|
||||
// No need for a `return` since the switch is exhaustive.
|
||||
}
|
||||
|
||||
bool simpleInference(B<num> b) {
|
||||
switch (b) {
|
||||
case C(listOfT: var x):
|
||||
x.expectStaticType<Exactly<List<num>>>();
|
||||
b.expectStaticType<Exactly<C<num>>>();
|
||||
return true;
|
||||
}
|
||||
// No need for a `return` since the switch is exhaustive.
|
||||
}
|
||||
|
||||
void inferDynamic(Object o) {
|
||||
switch (o) {
|
||||
case C(listOfT: var x, t: var t):
|
||||
x.expectStaticType<Exactly<List<dynamic>>>();
|
||||
// TODO(paulberry): make the following like work properly
|
||||
// t.foo(); // Should be ok since T is `dynamic`.
|
||||
o.expectStaticType<Exactly<C<dynamic>>>();
|
||||
default:
|
||||
Expect.fail('Match failure');
|
||||
}
|
||||
}
|
||||
|
||||
class D<T extends num> {
|
||||
final T t;
|
||||
|
||||
// Note: we use this getter to obtain values for `expectStaticType` (rather
|
||||
// than a getter returning simply `T`) to ensure that an error is reported if
|
||||
// `T` gets inferred to be `dynamic`.
|
||||
List<T> get listOfT => [t];
|
||||
|
||||
D(this.t);
|
||||
}
|
||||
|
||||
void inferBound(Object o) {
|
||||
switch (o) {
|
||||
case D(listOfT: var x):
|
||||
x.expectStaticType<Exactly<List<num>>>();
|
||||
o.expectStaticType<Exactly<D<num>>>();
|
||||
default:
|
||||
Expect.fail('Match failure');
|
||||
}
|
||||
}
|
||||
|
||||
class E<T, U> {
|
||||
final T t;
|
||||
final U u;
|
||||
|
||||
List<T> get listOfT => [t];
|
||||
List<U> get listOfU => [u];
|
||||
|
||||
E(this.t, this.u);
|
||||
|
||||
bool inferEnclosingTypeParameters(E<Set<U>, Set<T>> e) {
|
||||
// This test verifies that the inference logic properly distinguishes
|
||||
// between the type parameters of the enclosing class and those that are
|
||||
// part of the type of `e`.
|
||||
if (e case E(listOfT: var x, listOfU: var y)) {
|
||||
x.expectStaticType<Exactly<List<Set<U>>>>();
|
||||
y.expectStaticType<Exactly<List<Set<T>>>>();
|
||||
return true;
|
||||
}
|
||||
// No need for a `return` since the case fully covers the type of `e`.
|
||||
}
|
||||
}
|
||||
|
||||
main() {
|
||||
explicitTypeArguments(C(0));
|
||||
simpleInference(C(0));
|
||||
inferDynamic(C(0));
|
||||
inferBound(D(0));
|
||||
E<int, String>(0, '')
|
||||
.inferEnclosingTypeParameters(E<Set<String>, Set<int>>({''}, {0}));
|
||||
}
|
Loading…
Reference in a new issue