From c175d483433fb53056a0918cab4f7ffff3ebdcb1 Mon Sep 17 00:00:00 2001 From: Paul Berry Date: Fri, 24 Mar 2023 12:20:08 +0000 Subject: [PATCH] Front end: fix type inference for object patterns that refer to typedefs. I've also added some runtime checks to `tests/language/patterns/object_pattern_inference_test.dart` to verify that the object patterns match when they should match and don't match when they shouldn't; this helps verify that not only has the front end inferred the correct type for the object patterns, but it has also stored the correct type in the kernel representation. Fixes #51795. Bug: https://github.com/dart-lang/sdk/issues/51795 Change-Id: I73ce43e440db50d9942deb4a1eb4ee68a5c23142 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/290900 Reviewed-by: Chloe Stefantsova Commit-Queue: Paul Berry --- .../type_inference/inference_visitor.dart | 57 ++++++++----- .../object_pattern_inference_test.dart | 82 +++++++++++++++---- 2 files changed, 103 insertions(+), 36 deletions(-) diff --git a/pkg/front_end/lib/src/fasta/type_inference/inference_visitor.dart b/pkg/front_end/lib/src/fasta/type_inference/inference_visitor.dart index 67247fd1483..4b3243dcbe7 100644 --- a/pkg/front_end/lib/src/fasta/type_inference/inference_visitor.dart +++ b/pkg/front_end/lib/src/fasta/type_inference/inference_visitor.dart @@ -10925,30 +10925,45 @@ class InferenceVisitorImpl extends InferenceVisitorBase }) { DartType requiredType = pattern.requiredType; if (!pattern.hasExplicitTypeArguments) { - if (pattern.typedef != null) { - // TODO(paulberry): handle typedefs properly. - } - if (requiredType is InterfaceType && - requiredType.classNode.typeParameters.isNotEmpty) { + Typedef? typedef = pattern.typedef; + if (typedef != null) { + List typedefTypeParameters = typedef.typeParameters; + if (typedefTypeParameters.isNotEmpty) { + List asTypeArguments = + getAsTypeArguments(typedefTypeParameters, libraryBuilder.library); + TypedefType typedefType = new TypedefType( + typedef, libraryBuilder.library.nonNullable, asTypeArguments); + DartType unaliasedTypedef = typedefType.unalias; + List inferredTypeArguments = _inferTypeArguments( + typeParameters: typedefTypeParameters, + declaredType: unaliasedTypedef, + contextType: matchedType); + requiredType = new TypedefType(typedef, + libraryBuilder.library.nonNullable, inferredTypeArguments) + .unalias; + } + } else if (requiredType is InterfaceType) { List typeParameters = requiredType.classNode.typeParameters; + if (typeParameters.isNotEmpty) { + // 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; - // 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 inferredTypeArguments = _inferTypeArguments( - typeParameters: typeParameters, - declaredType: declaredType, - contextType: matchedType); - requiredType = new InterfaceType(requiredType.classNode, - requiredType.declaredNullability, inferredTypeArguments); + List inferredTypeArguments = _inferTypeArguments( + typeParameters: typeParameters, + declaredType: declaredType, + contextType: matchedType); + requiredType = new InterfaceType(requiredType.classNode, + requiredType.declaredNullability, inferredTypeArguments); + } } } return requiredType; diff --git a/tests/language/patterns/object_pattern_inference_test.dart b/tests/language/patterns/object_pattern_inference_test.dart index 745ca1731dd..ea1ed13e722 100644 --- a/tests/language/patterns/object_pattern_inference_test.dart +++ b/tests/language/patterns/object_pattern_inference_test.dart @@ -44,14 +44,15 @@ bool simpleInference(B b) { // No need for a `return` since the switch is exhaustive. } -void inferDynamic(Object o) { +bool inferDynamic(Object o) { switch (o) { case C(listOfT: var x, t: var t): x.expectStaticType>>(); t.isEven; // Should be ok since T is `dynamic`. o.expectStaticType>>(); + return true; default: - Expect.fail('Match failure'); + return false; } } @@ -66,13 +67,14 @@ class D { D(this.t); } -void inferBound(Object o) { +bool inferBound(Object o) { switch (o) { case D(listOfT: var x): x.expectStaticType>>(); o.expectStaticType>>(); + return true; default: - Expect.fail('Match failure'); + return false; } } @@ -110,13 +112,14 @@ class F2 extends F1 { } } -void fBounded(Object o) { +bool fBounded(Object o) { switch (o) { case F1(listOfT: var x): x.expectStaticType>>>(); o.expectStaticType>>>(); + return true; default: - Expect.fail('Match failure'); + return false; } } @@ -136,22 +139,71 @@ class G2> extends G1 { G2(super.t, this.u); } -void partialInference(G1 g) { +bool partialInference(G1 g) { switch (g) { case G2(listOfT: var x, listOfU: var y): x.expectStaticType>>(); y.expectStaticType>>>(); g.expectStaticType>>>(); + return true; + default: + return false; + } +} + +class H1 { + final T t; + final U u; + + List get listOfT => [t]; + List get listOfU => [u]; + + H1(this.t, this.u); +} + +typedef H2 = H1; + +bool typedefResolvingToInterfaceType(Object o) { + switch (o) { + case H2(listOfT: var x, listOfU: var y): + x.expectStaticType>>(); + y.expectStaticType>>(); + o.expectStaticType>>(); + return true; + default: + return false; + } +} + +typedef I = String Function(S); + +bool typedefResolvingToFunctionType(Object o) { + switch (o) { + case I(): + o.expectStaticType>>(); + return true; + default: + return false; } } main() { - explicitTypeArguments(C(0)); - simpleInference(C(0)); - inferDynamic(C(0)); - inferBound(D(0)); - E(0, '') - .inferEnclosingTypeParameters(E, Set>({''}, {0})); - fBounded(F2()); - partialInference(G2(0, {0})); + Expect.isTrue(explicitTypeArguments(C(0))); + Expect.isTrue(simpleInference(C(0))); + Expect.isTrue(inferDynamic(C(0))); + Expect.isFalse(inferDynamic(0)); + Expect.isTrue(inferBound(D(0))); + Expect.isFalse(inferBound(0)); + Expect.isTrue(E(0, '') + .inferEnclosingTypeParameters(E, Set>({''}, {0}))); + Expect.isTrue(fBounded(F2())); + Expect.isFalse(fBounded(0)); + Expect.isTrue(partialInference(G2(0, {0}))); + Expect.isFalse(partialInference(G1(0))); + Expect.isTrue(typedefResolvingToInterfaceType(H1(0, ''))); + Expect.isTrue(typedefResolvingToInterfaceType(H1(0, ''))); + Expect.isFalse(typedefResolvingToInterfaceType(H1(0, ''))); + Expect.isTrue(typedefResolvingToFunctionType((Object o) => o.toString())); + Expect.isTrue(typedefResolvingToFunctionType((num n) => n.toString())); + Expect.isFalse(typedefResolvingToFunctionType((int i) => i.toString())); }