From 724f2e6e7b401d148db8c4223330c9ede4954d9b Mon Sep 17 00:00:00 2001 From: Svetlana Isakova Date: Mon, 24 Nov 2014 18:10:23 +0300 Subject: [PATCH] KT-6081 Chained generic method calls: wrong type inference #KT-6081 Fixed --- .../kt6081SubstituteIntoClassCorrectly.kt | 14 +++++ .../kt6081SubstituteIntoClassCorrectly.txt | 19 ++++++ ...tionCheckTypeArgumentsNotTypeParameters.kt | 10 +++ ...ionCheckTypeArgumentsNotTypeParameters.txt | 12 ++++ .../substitutionIntoAnonymousClass.kt | 25 ++++++++ .../substitutionIntoAnonymousClass.txt | 20 ++++++ .../substitutionIntoInnerClass.kt | 22 +++++++ .../substitutionIntoInnerClass.txt | 35 +++++++++++ .../checkers/JetDiagnosticsTestGenerated.java | 35 ++++++++++- .../impl/LazySubstitutingClassDescriptor.java | 8 ++- .../jet/lang/types/TypeSubstitutor.java | 51 +++++++++++----- .../org/jetbrains/jet/lang/types/TypeUtils.kt | 61 +++++++++++++++++++ 12 files changed, 295 insertions(+), 17 deletions(-) create mode 100644 compiler/testData/diagnostics/tests/inference/substitutions/kt6081SubstituteIntoClassCorrectly.kt create mode 100644 compiler/testData/diagnostics/tests/inference/substitutions/kt6081SubstituteIntoClassCorrectly.txt create mode 100644 compiler/testData/diagnostics/tests/inference/substitutions/simpleSubstitutionCheckTypeArgumentsNotTypeParameters.kt create mode 100644 compiler/testData/diagnostics/tests/inference/substitutions/simpleSubstitutionCheckTypeArgumentsNotTypeParameters.txt create mode 100644 compiler/testData/diagnostics/tests/inference/substitutions/substitutionIntoAnonymousClass.kt create mode 100644 compiler/testData/diagnostics/tests/inference/substitutions/substitutionIntoAnonymousClass.txt create mode 100644 compiler/testData/diagnostics/tests/inference/substitutions/substitutionIntoInnerClass.kt create mode 100644 compiler/testData/diagnostics/tests/inference/substitutions/substitutionIntoInnerClass.txt create mode 100644 core/descriptors/src/org/jetbrains/jet/lang/types/TypeUtils.kt diff --git a/compiler/testData/diagnostics/tests/inference/substitutions/kt6081SubstituteIntoClassCorrectly.kt b/compiler/testData/diagnostics/tests/inference/substitutions/kt6081SubstituteIntoClassCorrectly.kt new file mode 100644 index 00000000000..43265e01c94 --- /dev/null +++ b/compiler/testData/diagnostics/tests/inference/substitutions/kt6081SubstituteIntoClassCorrectly.kt @@ -0,0 +1,14 @@ +// !DIAGNOSTICS: -UNUSED_PARAMETER +//KT-6081 Chained generic method calls: wrong type inference + +class Bar + +fun bar(): Bar = null!! + +class Foo { + fun add(bar: Bar): Foo {return this} +} + +fun doesNotWork(bi: Bar, bs: Bar) { + Foo().add(bi).add(bs) +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/inference/substitutions/kt6081SubstituteIntoClassCorrectly.txt b/compiler/testData/diagnostics/tests/inference/substitutions/kt6081SubstituteIntoClassCorrectly.txt new file mode 100644 index 00000000000..1a62eb12b0f --- /dev/null +++ b/compiler/testData/diagnostics/tests/inference/substitutions/kt6081SubstituteIntoClassCorrectly.txt @@ -0,0 +1,19 @@ +package + +internal fun bar(): Bar +internal fun doesNotWork(/*0*/ bi: Bar, /*1*/ bs: Bar): kotlin.Unit + +internal final class Bar { + public constructor Bar() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +internal final class Foo { + public constructor Foo() + internal final fun add(/*0*/ bar: Bar): Foo + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/compiler/testData/diagnostics/tests/inference/substitutions/simpleSubstitutionCheckTypeArgumentsNotTypeParameters.kt b/compiler/testData/diagnostics/tests/inference/substitutions/simpleSubstitutionCheckTypeArgumentsNotTypeParameters.kt new file mode 100644 index 00000000000..eec8a7694e5 --- /dev/null +++ b/compiler/testData/diagnostics/tests/inference/substitutions/simpleSubstitutionCheckTypeArgumentsNotTypeParameters.kt @@ -0,0 +1,10 @@ +class A { + fun useT(t: T) = t + + fun newA(): A = A() +} + +fun test1() { + A().newA().useT("") + A().newA().useT(1) +} diff --git a/compiler/testData/diagnostics/tests/inference/substitutions/simpleSubstitutionCheckTypeArgumentsNotTypeParameters.txt b/compiler/testData/diagnostics/tests/inference/substitutions/simpleSubstitutionCheckTypeArgumentsNotTypeParameters.txt new file mode 100644 index 00000000000..0404e67d3ab --- /dev/null +++ b/compiler/testData/diagnostics/tests/inference/substitutions/simpleSubstitutionCheckTypeArgumentsNotTypeParameters.txt @@ -0,0 +1,12 @@ +package + +internal fun test1(): kotlin.Unit + +internal final class A { + public constructor A() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + internal final fun newA(): A + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + internal final fun useT(/*0*/ t: T): T +} diff --git a/compiler/testData/diagnostics/tests/inference/substitutions/substitutionIntoAnonymousClass.kt b/compiler/testData/diagnostics/tests/inference/substitutions/substitutionIntoAnonymousClass.kt new file mode 100644 index 00000000000..5e3576bc416 --- /dev/null +++ b/compiler/testData/diagnostics/tests/inference/substitutions/substitutionIntoAnonymousClass.kt @@ -0,0 +1,25 @@ +class Test { + private fun T.self() = object { + fun bar(): T { + return this@self + } + } + fun test() { + 1.self().bar() + 1 + } +} + +class Foo { + private fun bar() = object { + fun baz(): Foo { + return this@Foo + } + } + + fun getR(r: R) = r + + fun test() { + Foo().bar().baz().getR(1) + Foo().bar().baz().getR("") + } +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/inference/substitutions/substitutionIntoAnonymousClass.txt b/compiler/testData/diagnostics/tests/inference/substitutions/substitutionIntoAnonymousClass.txt new file mode 100644 index 00000000000..39d1f37e5c9 --- /dev/null +++ b/compiler/testData/diagnostics/tests/inference/substitutions/substitutionIntoAnonymousClass.txt @@ -0,0 +1,20 @@ +package + +internal final class Foo { + public constructor Foo() + private final fun bar(): Foo.bar. + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + internal final fun getR(/*0*/ r: R): R + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + internal final fun test(): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +internal final class Test { + public constructor Test() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + internal final fun test(): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + private final fun T.self(): Test.self. +} diff --git a/compiler/testData/diagnostics/tests/inference/substitutions/substitutionIntoInnerClass.kt b/compiler/testData/diagnostics/tests/inference/substitutions/substitutionIntoInnerClass.kt new file mode 100644 index 00000000000..24ec182b8ef --- /dev/null +++ b/compiler/testData/diagnostics/tests/inference/substitutions/substitutionIntoInnerClass.kt @@ -0,0 +1,22 @@ +// !DIAGNOSTICS: -UNUSED_PARAMETER + +class Outer { + inner class Inner { + fun getOuter(): Outer = this@Outer + fun genericFun(r: R): Outer = this@Outer + } + fun useOuterParam(t: T) {} +} + +fun test1() { + Outer().Inner().getOuter().useOuterParam(22) + Outer().Inner().getOuter().useOuterParam("") +} + + +class A +class B + +fun test1(a: A, b: B) { + Outer().Inner().genericFun(a).Inner().genericFun(b) +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/inference/substitutions/substitutionIntoInnerClass.txt b/compiler/testData/diagnostics/tests/inference/substitutions/substitutionIntoInnerClass.txt new file mode 100644 index 00000000000..e2720eef6eb --- /dev/null +++ b/compiler/testData/diagnostics/tests/inference/substitutions/substitutionIntoInnerClass.txt @@ -0,0 +1,35 @@ +package + +internal fun test1(): kotlin.Unit +internal fun test1(/*0*/ a: A, /*1*/ b: B): kotlin.Unit + +internal final class A { + public constructor A() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +internal final class B { + public constructor B() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +internal final class Outer { + public constructor Outer() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + internal final fun useOuterParam(/*0*/ t: T): kotlin.Unit + + internal final inner class Inner { + public constructor Inner() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + internal final fun genericFun(/*0*/ r: R): Outer + internal final fun getOuter(): Outer + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } +} diff --git a/compiler/tests/org/jetbrains/jet/checkers/JetDiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/jet/checkers/JetDiagnosticsTestGenerated.java index f9757e56bc7..49f45a31c00 100644 --- a/compiler/tests/org/jetbrains/jet/checkers/JetDiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/jet/checkers/JetDiagnosticsTestGenerated.java @@ -4806,7 +4806,7 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest { @TestMetadata("compiler/testData/diagnostics/tests/inference") @TestDataPath("$PROJECT_ROOT") - @InnerTestClasses({Inference.Constraints.class, Inference.NestedCalls.class, Inference.Regressions.class, Inference.ReportingImprovements.class, Inference.UpperBounds.class, Inference.Varargs.class}) + @InnerTestClasses({Inference.Constraints.class, Inference.NestedCalls.class, Inference.Regressions.class, Inference.ReportingImprovements.class, Inference.Substitutions.class, Inference.UpperBounds.class, Inference.Varargs.class}) @RunWith(JUnit3RunnerWithInners.class) public static class Inference extends AbstractJetDiagnosticsTest { public void testAllFilesPresentInInference() throws Exception { @@ -5419,6 +5419,39 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest { } } + @TestMetadata("compiler/testData/diagnostics/tests/inference/substitutions") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Substitutions extends AbstractJetDiagnosticsTest { + public void testAllFilesPresentInSubstitutions() throws Exception { + JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/diagnostics/tests/inference/substitutions"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("kt6081SubstituteIntoClassCorrectly.kt") + public void testKt6081SubstituteIntoClassCorrectly() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/inference/substitutions/kt6081SubstituteIntoClassCorrectly.kt"); + doTest(fileName); + } + + @TestMetadata("simpleSubstitutionCheckTypeArgumentsNotTypeParameters.kt") + public void testSimpleSubstitutionCheckTypeArgumentsNotTypeParameters() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/inference/substitutions/simpleSubstitutionCheckTypeArgumentsNotTypeParameters.kt"); + doTest(fileName); + } + + @TestMetadata("substitutionIntoAnonymousClass.kt") + public void testSubstitutionIntoAnonymousClass() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/inference/substitutions/substitutionIntoAnonymousClass.kt"); + doTest(fileName); + } + + @TestMetadata("substitutionIntoInnerClass.kt") + public void testSubstitutionIntoInnerClass() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/inference/substitutions/substitutionIntoInnerClass.kt"); + doTest(fileName); + } + } + @TestMetadata("compiler/testData/diagnostics/tests/inference/upperBounds") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/core/descriptors/src/org/jetbrains/jet/lang/descriptors/impl/LazySubstitutingClassDescriptor.java b/core/descriptors/src/org/jetbrains/jet/lang/descriptors/impl/LazySubstitutingClassDescriptor.java index a27a289f081..c8b1ea6584a 100644 --- a/core/descriptors/src/org/jetbrains/jet/lang/descriptors/impl/LazySubstitutingClassDescriptor.java +++ b/core/descriptors/src/org/jetbrains/jet/lang/descriptors/impl/LazySubstitutingClassDescriptor.java @@ -106,7 +106,13 @@ public class LazySubstitutingClassDescriptor implements ClassDescriptor { @NotNull @Override public JetType getDefaultType() { - throw new UnsupportedOperationException(); // TODO + List typeProjections = TypeUtils.getDefaultTypeProjections(getTypeConstructor().getParameters()); + return new JetTypeImpl( + getAnnotations(), + getTypeConstructor(), + false, + typeProjections, + getMemberScope(typeProjections)); } @NotNull diff --git a/core/descriptors/src/org/jetbrains/jet/lang/types/TypeSubstitutor.java b/core/descriptors/src/org/jetbrains/jet/lang/types/TypeSubstitutor.java index 53a0d4d5cbf..54c2358ee56 100644 --- a/core/descriptors/src/org/jetbrains/jet/lang/types/TypeSubstitutor.java +++ b/core/descriptors/src/org/jetbrains/jet/lang/types/TypeSubstitutor.java @@ -21,11 +21,9 @@ import org.jetbrains.annotations.Nullable; import org.jetbrains.jet.lang.descriptors.TypeParameterDescriptor; import org.jetbrains.jet.lang.resolve.scopes.SubstitutingScope; import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns; +import org.jetbrains.jet.lang.types.typeUtil.TypeUtilPackage; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; public class TypeSubstitutor { @@ -202,18 +200,41 @@ public class TypeSubstitutor { throw new IllegalStateException(); } } - else { - // The type is not within the substitution range, i.e. Foo, Bar etc. - List substitutedArguments = substituteTypeArguments( - type.getConstructor().getParameters(), type.getArguments(), recursionDepth); + // The type is not within the substitution range, i.e. Foo, Bar etc. + return substituteCompoundType(type, originalProjectionKind, recursionDepth); + } - JetType substitutedType = new JetTypeImpl(type.getAnnotations(), // Old annotations. This is questionable - type.getConstructor(), // The same constructor - type.isNullable(), // Same nullability - substitutedArguments, - new SubstitutingScope(type.getMemberScope(), this)); - return new TypeProjectionImpl(originalProjectionKind, substitutedType); - } + private TypeProjection substituteCompoundType( + final JetType type, + Variance projectionKind, + int recursionDepth + ) throws SubstitutionException { + List substitutedArguments = substituteTypeArguments( + type.getConstructor().getParameters(), type.getArguments(), recursionDepth); + + // Only type parameters of the corresponding class (or captured type parameters of outer declaration) are substituted + // e.g. for return type Foo of 'add(..)' in 'class Foo { fun add(bar: Bar): Foo }' R shouldn't be substituted in the scope + TypeSubstitution substitutionFilteringTypeParameters = new TypeSubstitution() { + private final Collection containedOrCapturedTypeParameters = + TypeUtilPackage.getContainedAndCapturedTypeParameterConstructors(type); + + @Nullable + @Override + public TypeProjection get(TypeConstructor key) { + return containedOrCapturedTypeParameters.contains(key) ? substitution.get(key) : null; + } + + @Override + public boolean isEmpty() { + return substitution.isEmpty(); + } + }; + JetType substitutedType = new JetTypeImpl(type.getAnnotations(), // Old annotations. This is questionable + type.getConstructor(), // The same constructor + type.isNullable(), // Same nullability + substitutedArguments, + new SubstitutingScope(type.getMemberScope(), create(substitutionFilteringTypeParameters))); + return new TypeProjectionImpl(projectionKind, substitutedType); } private List substituteTypeArguments( diff --git a/core/descriptors/src/org/jetbrains/jet/lang/types/TypeUtils.kt b/core/descriptors/src/org/jetbrains/jet/lang/types/TypeUtils.kt new file mode 100644 index 00000000000..2d41316f4a5 --- /dev/null +++ b/core/descriptors/src/org/jetbrains/jet/lang/types/TypeUtils.kt @@ -0,0 +1,61 @@ +/* + * Copyright 2010-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.jet.lang.types.typeUtil + +import java.util.LinkedHashSet +import org.jetbrains.jet.lang.descriptors.CallableDescriptor +import org.jetbrains.jet.lang.descriptors.ClassDescriptor +import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor +import org.jetbrains.jet.lang.descriptors.TypeParameterDescriptor +import org.jetbrains.jet.lang.types.JetType +import org.jetbrains.jet.lang.types.Flexibility +import org.jetbrains.jet.lang.types.TypeConstructor +import org.jetbrains.jet.utils.toReadOnlyList + +fun JetType.getContainedTypeParameters(): Collection { + val declarationDescriptor = getConstructor().getDeclarationDescriptor() + if (declarationDescriptor is TypeParameterDescriptor) return listOf(declarationDescriptor) + + val flexibility = getCapability(javaClass()) + if (flexibility != null) { + return flexibility.lowerBound.getContainedTypeParameters() + flexibility.upperBound.getContainedTypeParameters() + } + return getArguments().map { it.getType() }.flatMap { it.getContainedTypeParameters() } +} + +fun DeclarationDescriptor.getCapturedTypeParameters(): Collection { + val result = LinkedHashSet() + val containingDeclaration = this.getContainingDeclaration() + + if (containingDeclaration is ClassDescriptor) { + result.addAll(containingDeclaration.getDefaultType().getContainedTypeParameters()) + } + else if (containingDeclaration is CallableDescriptor) { + result.addAll(containingDeclaration.getTypeParameters()) + } + if (containingDeclaration != null) { + result.addAll(containingDeclaration.getCapturedTypeParameters()) + } + return result +} + +public fun JetType.getContainedAndCapturedTypeParameterConstructors(): Collection { + // todo type arguments (instead of type parameters) of the type of outer class must be considered; KT-6325 + val typeParameters = getContainedTypeParameters() + getConstructor().getDeclarationDescriptor().getCapturedTypeParameters() + return typeParameters.map { it.getTypeConstructor() }.toReadOnlyList() +} +