diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt index 7d13edb8518..739f088d2a4 100644 --- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt +++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt @@ -4976,6 +4976,14 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert token, ) } + add(FirErrors.BUILDER_INFERENCE_MULTI_LAMBDA_RESTRICTION) { firDiagnostic -> + BuilderInferenceMultiLambdaRestrictionImpl( + firDiagnostic.a, + firDiagnostic.b, + firDiagnostic as KtPsiDiagnostic, + token, + ) + } add(FirJvmErrors.OVERRIDE_CANNOT_BE_STATIC) { firDiagnostic -> OverrideCannotBeStaticImpl( firDiagnostic as KtPsiDiagnostic, diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt index 351f22cbd67..171e31036a0 100644 --- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt +++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt @@ -3463,6 +3463,12 @@ sealed interface KtFirDiagnostic : KtDiagnosticWithPsi { val containingDeclarationName: Name } + interface BuilderInferenceMultiLambdaRestriction : KtFirDiagnostic { + override val diagnosticClass get() = BuilderInferenceMultiLambdaRestriction::class + val typeParameterName: Name + val containingDeclarationName: Name + } + interface OverrideCannotBeStatic : KtFirDiagnostic { override val diagnosticClass get() = OverrideCannotBeStatic::class } diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt index 31905efcfa5..af3d723fd58 100644 --- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt +++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt @@ -4176,6 +4176,13 @@ internal class BuilderInferenceStubReceiverImpl( token: KtLifetimeToken, ) : KtAbstractFirDiagnostic(firDiagnostic, token), KtFirDiagnostic.BuilderInferenceStubReceiver +internal class BuilderInferenceMultiLambdaRestrictionImpl( + override val typeParameterName: Name, + override val containingDeclarationName: Name, + firDiagnostic: KtPsiDiagnostic, + token: KtLifetimeToken, +) : KtAbstractFirDiagnostic(firDiagnostic, token), KtFirDiagnostic.BuilderInferenceMultiLambdaRestriction + internal class OverrideCannotBeStaticImpl( firDiagnostic: KtPsiDiagnostic, token: KtLifetimeToken, diff --git a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosticCompilerTestFE10TestdataTestGenerated.java b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosticCompilerTestFE10TestdataTestGenerated.java index 164000e13f3..a7eb3ff08e0 100644 --- a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosticCompilerTestFE10TestdataTestGenerated.java +++ b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosticCompilerTestFE10TestdataTestGenerated.java @@ -16733,6 +16733,12 @@ public class DiagnosticCompilerTestFE10TestdataTestGenerated extends AbstractDia KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/tests/inference/builderInference"), Pattern.compile("^(.+)\\.(kt|kts)$"), Pattern.compile("^(.+)\\.(reversed|fir|ll)\\.kts?$"), true); } + @Test + @TestMetadata("builderInferenceAnnotationInLambdaWithTVExpectedType.kt") + public void testBuilderInferenceAnnotationInLambdaWithTVExpectedType() throws Exception { + runTest("compiler/testData/diagnostics/tests/inference/builderInference/builderInferenceAnnotationInLambdaWithTVExpectedType.kt"); + } + @Test @TestMetadata("changingResolveIfDontUseBuilderInference.kt") public void testChangingResolveIfDontUseBuilderInference() throws Exception { diff --git a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/LLFirPreresolvedReversedDiagnosticCompilerFE10TestDataTestGenerated.java b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/LLFirPreresolvedReversedDiagnosticCompilerFE10TestDataTestGenerated.java index fedabc15083..e17c25160ce 100644 --- a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/LLFirPreresolvedReversedDiagnosticCompilerFE10TestDataTestGenerated.java +++ b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/LLFirPreresolvedReversedDiagnosticCompilerFE10TestDataTestGenerated.java @@ -16733,6 +16733,12 @@ public class LLFirPreresolvedReversedDiagnosticCompilerFE10TestDataTestGenerated KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/tests/inference/builderInference"), Pattern.compile("^(.+)\\.(kt|kts)$"), Pattern.compile("^(.+)\\.(reversed|fir|ll)\\.kts?$"), true); } + @Test + @TestMetadata("builderInferenceAnnotationInLambdaWithTVExpectedType.kt") + public void testBuilderInferenceAnnotationInLambdaWithTVExpectedType() throws Exception { + runTest("compiler/testData/diagnostics/tests/inference/builderInference/builderInferenceAnnotationInLambdaWithTVExpectedType.kt"); + } + @Test @TestMetadata("changingResolveIfDontUseBuilderInference.kt") public void testChangingResolveIfDontUseBuilderInference() throws Exception { diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirLightTreeOldFrontendDiagnosticsTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirLightTreeOldFrontendDiagnosticsTestGenerated.java index e84d91a5ea8..4745937dc7e 100644 --- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirLightTreeOldFrontendDiagnosticsTestGenerated.java +++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirLightTreeOldFrontendDiagnosticsTestGenerated.java @@ -16727,6 +16727,12 @@ public class FirLightTreeOldFrontendDiagnosticsTestGenerated extends AbstractFir KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/tests/inference/builderInference"), Pattern.compile("^(.+)\\.kt$"), Pattern.compile("^(.+)\\.(reversed|fir|ll)\\.kts?$"), true, "multiplatform"); } + @Test + @TestMetadata("builderInferenceAnnotationInLambdaWithTVExpectedType.kt") + public void testBuilderInferenceAnnotationInLambdaWithTVExpectedType() throws Exception { + runTest("compiler/testData/diagnostics/tests/inference/builderInference/builderInferenceAnnotationInLambdaWithTVExpectedType.kt"); + } + @Test @TestMetadata("changingResolveIfDontUseBuilderInference.kt") public void testChangingResolveIfDontUseBuilderInference() throws Exception { diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirPsiOldFrontendDiagnosticsTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirPsiOldFrontendDiagnosticsTestGenerated.java index bf25341699a..8a77adbfa7c 100644 --- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirPsiOldFrontendDiagnosticsTestGenerated.java +++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirPsiOldFrontendDiagnosticsTestGenerated.java @@ -16733,6 +16733,12 @@ public class FirPsiOldFrontendDiagnosticsTestGenerated extends AbstractFirPsiDia KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/tests/inference/builderInference"), Pattern.compile("^(.*)\\.kts?$"), Pattern.compile("^(.+)\\.(reversed|fir|ll)\\.kts?$"), true, "multiplatform"); } + @Test + @TestMetadata("builderInferenceAnnotationInLambdaWithTVExpectedType.kt") + public void testBuilderInferenceAnnotationInLambdaWithTVExpectedType() throws Exception { + runTest("compiler/testData/diagnostics/tests/inference/builderInference/builderInferenceAnnotationInLambdaWithTVExpectedType.kt"); + } + @Test @TestMetadata("changingResolveIfDontUseBuilderInference.kt") public void testChangingResolveIfDontUseBuilderInference() throws Exception { diff --git a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt index f715438c4b2..2f56166b5d8 100644 --- a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt +++ b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt @@ -1770,6 +1770,10 @@ object DIAGNOSTICS_LIST : DiagnosticList("FirErrors") { parameter("typeParameterName") parameter("containingDeclarationName") } + val BUILDER_INFERENCE_MULTI_LAMBDA_RESTRICTION by error { + parameter("typeParameterName") + parameter("containingDeclarationName") + } } } diff --git a/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt b/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt index 7164e27d7f3..50fe8906167 100644 --- a/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt +++ b/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt @@ -871,6 +871,7 @@ object FirErrors { // Builder inference val BUILDER_INFERENCE_STUB_RECEIVER by error2() + val BUILDER_INFERENCE_MULTI_LAMBDA_RESTRICTION by error2() init { RootDiagnosticRendererFactory.registerFactory(FirErrorsDefaultMessages) diff --git a/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirNonSuppressibleErrorNames.kt b/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirNonSuppressibleErrorNames.kt index 3a32901c866..8403a1b2112 100644 --- a/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirNonSuppressibleErrorNames.kt +++ b/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirNonSuppressibleErrorNames.kt @@ -572,6 +572,7 @@ val FIR_NON_SUPPRESSIBLE_ERROR_NAMES: Set = setOf( "PRE_RELEASE_CLASS", "IR_WITH_UNSTABLE_ABI_COMPILED_CLASS", "BUILDER_INFERENCE_STUB_RECEIVER", + "BUILDER_INFERENCE_MULTI_LAMBDA_RESTRICTION", "OVERRIDE_CANNOT_BE_STATIC", "JVM_STATIC_NOT_IN_OBJECT_OR_CLASS_COMPANION", "JVM_STATIC_NOT_IN_OBJECT_OR_COMPANION", diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrorsDefaultMessages.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrorsDefaultMessages.kt index 47f3a7fd0de..d1faef5c340 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrorsDefaultMessages.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrorsDefaultMessages.kt @@ -112,6 +112,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.BOUNDS_NOT_ALLOWE import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.BOUND_ON_TYPE_ALIAS_PARAMETER_NOT_ALLOWED import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.BREAK_OR_CONTINUE_JUMPS_ACROSS_FUNCTION_BOUNDARY import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.BREAK_OR_CONTINUE_OUTSIDE_A_LOOP +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.BUILDER_INFERENCE_MULTI_LAMBDA_RESTRICTION import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.BUILDER_INFERENCE_STUB_RECEIVER import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CALLABLE_REFERENCE_LHS_NOT_A_CLASS import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CALLABLE_REFERENCE_TO_ANNOTATION_CONSTRUCTOR @@ -2577,6 +2578,12 @@ object FirErrorsDefaultMessages : BaseDiagnosticRendererFactory() { "The type of a receiver hasn''t been inferred yet. Please specify type argument for generic parameter `{0}` of `{1}` explicitly", TO_STRING, TO_STRING - ); + ) + map.put( + BUILDER_INFERENCE_MULTI_LAMBDA_RESTRICTION, + "Unstable inference behaviour with multiple lambdas. Please either specify the type argument for generic parameter `{0}` of `{1}` explicitly", + TO_STRING, + TO_STRING + ) } } diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/coneDiagnosticToFirDiagnostic.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/coneDiagnosticToFirDiagnostic.kt index 04f2463b07f..08f1ede6129 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/coneDiagnosticToFirDiagnostic.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/coneDiagnosticToFirDiagnostic.kt @@ -16,6 +16,7 @@ import org.jetbrains.kotlin.fir.FirSession import org.jetbrains.kotlin.fir.analysis.checkers.declaration.isLocalMember import org.jetbrains.kotlin.fir.analysis.getChild import org.jetbrains.kotlin.fir.builder.FirSyntaxErrors +import org.jetbrains.kotlin.fir.declarations.FirAnonymousFunction import org.jetbrains.kotlin.fir.declarations.FirMemberDeclaration import org.jetbrains.kotlin.fir.declarations.utils.* import org.jetbrains.kotlin.fir.diagnostics.* @@ -23,6 +24,7 @@ import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccessExpression import org.jetbrains.kotlin.fir.references.toResolvedCallableSymbol import org.jetbrains.kotlin.fir.resolve.calls.* import org.jetbrains.kotlin.fir.resolve.diagnostics.* +import org.jetbrains.kotlin.fir.resolve.inference.AnonymousFunctionBasedMultiLambdaBuilderInferenceRestriction import org.jetbrains.kotlin.fir.resolve.inference.ConeTypeParameterBasedTypeVariable import org.jetbrains.kotlin.fir.resolve.inference.ConeTypeVariableForLambdaReturnType import org.jetbrains.kotlin.fir.resolve.inference.model.ConeArgumentConstraintPosition @@ -529,6 +531,18 @@ private fun ConstraintSystemError.toDiagnostic( ) } + is AnonymousFunctionBasedMultiLambdaBuilderInferenceRestriction -> { + val typeParameterSymbol = (typeParameter as ConeTypeParameterLookupTag).typeParameterSymbol + FirErrors.BUILDER_INFERENCE_MULTI_LAMBDA_RESTRICTION.createOn( + anonymous.source ?: source, + typeParameterSymbol.name, + @OptIn(SymbolInternals::class) + (typeParameterSymbol.containingDeclarationSymbol.fir as FirMemberDeclaration).nameOrSpecialName + ) + } + + is MultiLambdaBuilderInferenceRestriction<*> -> shouldNotBeCalled() + else -> null } } diff --git a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/resolve/inference/AnonymousFunctionBasedMultiLambdaBuilderInferenceRestriction.kt b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/resolve/inference/AnonymousFunctionBasedMultiLambdaBuilderInferenceRestriction.kt new file mode 100644 index 00000000000..d74f82c2e6e --- /dev/null +++ b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/resolve/inference/AnonymousFunctionBasedMultiLambdaBuilderInferenceRestriction.kt @@ -0,0 +1,15 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.fir.resolve.inference + +import org.jetbrains.kotlin.fir.declarations.FirAnonymousFunction +import org.jetbrains.kotlin.resolve.calls.inference.model.MultiLambdaBuilderInferenceRestriction +import org.jetbrains.kotlin.types.model.TypeParameterMarker + +class AnonymousFunctionBasedMultiLambdaBuilderInferenceRestriction( + anonymous: FirAnonymousFunction, + typeParameter: TypeParameterMarker +) : MultiLambdaBuilderInferenceRestriction(anonymous, typeParameter) diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/ResolutionStages.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/ResolutionStages.kt index fb580bcdbc8..9a709a5cb38 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/ResolutionStages.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/ResolutionStages.kt @@ -20,10 +20,7 @@ import org.jetbrains.kotlin.fir.references.FirSuperReference import org.jetbrains.kotlin.fir.references.FirThisReference import org.jetbrains.kotlin.fir.resolve.directExpansionType import org.jetbrains.kotlin.fir.resolve.fullyExpandedType -import org.jetbrains.kotlin.fir.resolve.inference.ConeTypeParameterBasedTypeVariable -import org.jetbrains.kotlin.fir.resolve.inference.ResolvedCallableReferenceAtom -import org.jetbrains.kotlin.fir.resolve.inference.csBuilder -import org.jetbrains.kotlin.fir.resolve.inference.hasBuilderInferenceAnnotation +import org.jetbrains.kotlin.fir.resolve.inference.* import org.jetbrains.kotlin.fir.resolve.inference.model.ConeExplicitTypeParameterConstraintPosition import org.jetbrains.kotlin.fir.resolve.toSymbol import org.jetbrains.kotlin.fir.scopes.FirTypeScope @@ -686,8 +683,17 @@ internal object PostponedVariablesInitializerResolutionStage : ResolutionStage() override suspend fun check(candidate: Candidate, callInfo: CallInfo, sink: CheckerSink, context: ResolutionContext) { val argumentMapping = candidate.argumentMapping ?: return if (candidate.typeArgumentMapping is TypeArgumentMapping.Mapped) return - for (parameter in argumentMapping.values) { + + val atomsToMark = mutableSetOf() + + for ((argument, parameter) in argumentMapping) { if (!parameter.hasBuilderInferenceAnnotation(context.session)) continue + val unwrapped = argument.unwrapArgument() + if (unwrapped is FirAnonymousFunctionExpression) { + atomsToMark.add(unwrapped.anonymousFunction) + } + + // TODO: This is effectively dead-code, since UseBuilderInferenceOnlyIfNeeded is always enabled val type = parameter.returnTypeRef.coneType val receiverType = type.receiverType(callInfo.session) ?: continue val dontUseBuilderInferenceIfPossible = @@ -706,6 +712,23 @@ internal object PostponedVariablesInitializerResolutionStage : ResolutionStage() } } } + + candidate.postponedAtoms.forEach { postponedAtomContainer -> + when (postponedAtomContainer) { + is ResolvedLambdaAtom -> { + if (postponedAtomContainer.atom in atomsToMark) { + postponedAtomContainer.isCorrespondingParameterAnnotatedWithBuilderInference = true + } + } + is LambdaWithTypeVariableAsExpectedTypeAtom -> { + // Although, one may annotate fun foo(@BuilderInference t: T) + // it makes little sense + } + is ResolvedCallableReferenceAtom -> { + // Builder inference doesn't apply to such atoms. + } + } + } } } diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/inference/ConstraintSystemCompleter.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/inference/ConstraintSystemCompleter.kt index 4433cdcdd60..cdd63d86ef9 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/inference/ConstraintSystemCompleter.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/inference/ConstraintSystemCompleter.kt @@ -33,6 +33,7 @@ import org.jetbrains.kotlin.resolve.calls.inference.model.VariableWithConstraint import org.jetbrains.kotlin.resolve.calls.model.PostponedAtomWithRevisableExpectedType import org.jetbrains.kotlin.types.model.TypeConstructorMarker import org.jetbrains.kotlin.types.model.TypeVariableMarker +import org.jetbrains.kotlin.types.model.TypeVariableTypeConstructorMarker import org.jetbrains.kotlin.utils.addIfNotNull import org.jetbrains.kotlin.utils.addToStdlib.filterIsInstanceWithChecker @@ -194,15 +195,44 @@ class ConstraintSystemCompleter(components: BodyResolveComponents, private val c val lambdaArguments = postponedArguments.filterIsInstance().takeIf { it.isNotEmpty() } ?: return false + fun ResolvedLambdaAtom.notFixedInputTypeVariables(): List = + inputTypes.flatMap { it.extractTypeVariables() }.filter { it !in fixedTypeVariables } + + val checkForDangerousBuilderInference = + !languageVersionSettings.supportsFeature(LanguageFeature.NoBuilderInferenceWithoutAnnotationRestriction) + + // Let's call builder lambda (BL) a lambda that has non-zero not fixed input type variables in + // type arguments of it's input types + // ex: MutableList.() -> Unit + // During type inference of call-site such lambda will be considered BL, if + // T not fixed yet + // Given we have two or more builder lambdas among postponed arguments, it could result in incorrect type inference due to + // incorrect constraint propagation into common system + // See KT-53740 + // Constraint propagation into common system happens at + // org.jetbrains.kotlin.resolve.calls.components.PostponedArgumentsAnalyzer.applyResultsOfAnalyzedLambdaToCandidateSystem + val dangerousBuilderInferenceWithoutAnnotation = + lambdaArguments.size >= 2 && lambdaArguments.count { it.notFixedInputTypeVariables().isNotEmpty() } >= 2 + // We assume useBuilderInferenceWithoutAnnotation = true for FIR val builder = getBuilder() for (argument in lambdaArguments) { - val notFixedInputTypeVariables = argument.inputTypes - .flatMap { it.extractTypeVariables() }.filter { it !in fixedTypeVariables } + val reallyHasBuilderInferenceAnnotation = argument.isCorrespondingParameterAnnotatedWithBuilderInference + + val notFixedInputTypeVariables = argument.notFixedInputTypeVariables() if (notFixedInputTypeVariables.isEmpty()) continue + // we have dangerous inference situation + // if lambda annotated with BuilderInference it's probably safe, due to type shape + // otherwise report multi-lambda builder inference restriction diagnostic + if (checkForDangerousBuilderInference && dangerousBuilderInferenceWithoutAnnotation && !reallyHasBuilderInferenceAnnotation) { + for (variable in notFixedInputTypeVariables) { + addError(AnonymousFunctionBasedMultiLambdaBuilderInferenceRestriction(argument.atom, variable.typeParameter!!)) + } + } + for (variable in notFixedInputTypeVariables) { builder.markPostponedVariable(notFixedTypeVariables.getValue(variable).typeVariable) } diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/inference/PostponedAtoms.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/inference/PostponedAtoms.kt index c9d934e5010..b3ca65c1084 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/inference/PostponedAtoms.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/inference/PostponedAtoms.kt @@ -77,6 +77,15 @@ class ResolvedLambdaAtom( fun replaceTypeVariableForLambdaReturnType(typeVariableForLambdaReturnType: ConeTypeVariableForLambdaReturnType) { this.typeVariableForLambdaReturnType = typeVariableForLambdaReturnType } + + /** + * Set to true by resolution stage, if the corresponding parameter has BuilderInference annotation + * ```kotlin + * fun foo(@BuilderInference b: Buildee.() -> Unit) {} + * fun test() = foo({ ... }) // Will be true for the lambda argument passed to b + * ``` + */ + var isCorrespondingParameterAnnotatedWithBuilderInference: Boolean = false } class LambdaWithTypeVariableAsExpectedTypeAtom( diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/DiagnosticReporterByTrackingStrategy.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/DiagnosticReporterByTrackingStrategy.kt index 21cdb7751d1..23c5ddaac02 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/DiagnosticReporterByTrackingStrategy.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/DiagnosticReporterByTrackingStrategy.kt @@ -27,6 +27,7 @@ import org.jetbrains.kotlin.resolve.calls.context.BasicCallResolutionContext import org.jetbrains.kotlin.resolve.calls.inference.BuilderInferenceExpectedTypeConstraintPosition import org.jetbrains.kotlin.resolve.calls.inference.model.* import org.jetbrains.kotlin.resolve.calls.model.* +import org.jetbrains.kotlin.resolve.calls.model.MultiLambdaBuilderInferenceRestriction import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValueFactory import org.jetbrains.kotlin.resolve.calls.smartcasts.SingleSmartCast import org.jetbrains.kotlin.resolve.calls.smartcasts.SmartCastManager @@ -55,6 +56,7 @@ import org.jetbrains.kotlin.types.model.freshTypeConstructor import org.jetbrains.kotlin.types.typeUtil.contains import org.jetbrains.kotlin.types.typeUtil.isNullableNothing import org.jetbrains.kotlin.types.typeUtil.makeNullable +import org.jetbrains.kotlin.utils.addToStdlib.shouldNotBeCalled import kotlin.contracts.ExperimentalContracts import kotlin.contracts.contract @@ -812,7 +814,9 @@ class DiagnosticReporterByTrackingStrategy( // LowerPriorityToPreserveCompatibility is not expected to report something is LowerPriorityToPreserveCompatibility -> {} // NoSuccessfulFork does not exist in K1 - is NoSuccessfulFork -> {} + is NoSuccessfulFork -> shouldNotBeCalled() + // MultiLambdaBuilderInferenceRestriction does not exist in K1 + is org.jetbrains.kotlin.resolve.calls.inference.model.MultiLambdaBuilderInferenceRestriction<*> -> shouldNotBeCalled() // NotEnoughInformationForTypeParameterImpl is already considered above is NotEnoughInformationForTypeParameter<*> -> { throw AssertionError("constraintError should not be called with ${error::class.java}") diff --git a/compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/inference/model/ConstraintPositionAndErrors.kt b/compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/inference/model/ConstraintPositionAndErrors.kt index d1254ef6cd0..978950078b0 100644 --- a/compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/inference/model/ConstraintPositionAndErrors.kt +++ b/compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/inference/model/ConstraintPositionAndErrors.kt @@ -9,6 +9,7 @@ import org.jetbrains.kotlin.resolve.calls.tower.CandidateApplicability import org.jetbrains.kotlin.resolve.calls.tower.CandidateApplicability.* import org.jetbrains.kotlin.types.EmptyIntersectionTypeKind import org.jetbrains.kotlin.types.model.KotlinTypeMarker +import org.jetbrains.kotlin.types.model.TypeParameterMarker import org.jetbrains.kotlin.types.model.TypeVariableMarker interface OnlyInputTypeConstraintPosition @@ -173,6 +174,11 @@ class OnlyInputTypesDiagnostic(val typeVariable: TypeVariableMarker) : Constrain class LowerPriorityToPreserveCompatibility(val needToReportWarning: Boolean) : ConstraintSystemError(RESOLVED_NEED_PRESERVE_COMPATIBILITY) +open class MultiLambdaBuilderInferenceRestriction( + val anonymous: T, + val typeParameter: TypeParameterMarker +) : ConstraintSystemError(RESOLVED_WITH_ERROR) + fun Constraint.isExpectedTypePosition() = position.from is ExpectedTypeConstraintPosition<*> || position.from is DelegatedPropertyConstraintPosition<*> diff --git a/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/inference/components/KotlinConstraintSystemCompleter.kt b/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/inference/components/KotlinConstraintSystemCompleter.kt index 2bd3cf0bef2..9585b4605d6 100644 --- a/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/inference/components/KotlinConstraintSystemCompleter.kt +++ b/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/inference/components/KotlinConstraintSystemCompleter.kt @@ -11,6 +11,7 @@ import org.jetbrains.kotlin.resolve.calls.components.candidate.SimpleResolutionC import org.jetbrains.kotlin.resolve.calls.components.transformToResolvedLambda import org.jetbrains.kotlin.resolve.calls.inference.model.* import org.jetbrains.kotlin.resolve.calls.model.* +import org.jetbrains.kotlin.resolve.calls.model.MultiLambdaBuilderInferenceRestriction import org.jetbrains.kotlin.types.* import org.jetbrains.kotlin.types.error.ErrorTypeKind import org.jetbrains.kotlin.types.error.ErrorUtils diff --git a/compiler/testData/diagnostics/tests/inference/builderInference/builderInferenceAnnotationInLambdaWithTVExpectedType.fir.kt b/compiler/testData/diagnostics/tests/inference/builderInference/builderInferenceAnnotationInLambdaWithTVExpectedType.fir.kt new file mode 100644 index 00000000000..9adc37f0f39 --- /dev/null +++ b/compiler/testData/diagnostics/tests/inference/builderInference/builderInferenceAnnotationInLambdaWithTVExpectedType.fir.kt @@ -0,0 +1,15 @@ +// WITH_STDLIB +package some + +import kotlin.experimental.ExperimentalTypeInference + +@OptIn(ExperimentalTypeInference::class) +fun applyBI(@BuilderInference t: T): T = t + +fun myBuildList(a: MutableList.() -> Unit) {} + +fun main() { + myBuildList(applyBI { + this.add("1") + }) +} diff --git a/compiler/testData/diagnostics/tests/inference/builderInference/builderInferenceAnnotationInLambdaWithTVExpectedType.kt b/compiler/testData/diagnostics/tests/inference/builderInference/builderInferenceAnnotationInLambdaWithTVExpectedType.kt new file mode 100644 index 00000000000..44163303325 --- /dev/null +++ b/compiler/testData/diagnostics/tests/inference/builderInference/builderInferenceAnnotationInLambdaWithTVExpectedType.kt @@ -0,0 +1,15 @@ +// WITH_STDLIB +package some + +import kotlin.experimental.ExperimentalTypeInference + +@OptIn(ExperimentalTypeInference::class) +fun applyBI(@BuilderInference t: T): T = t + +fun myBuildList(a: MutableList.() -> Unit) {} + +fun main() { + myBuildList(applyBI { + this.add("1") + }) +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/inference/builderInference/kt53422.fir.kt b/compiler/testData/diagnostics/tests/inference/builderInference/kt53422.fir.kt index d17a07d289f..9e7c7be6e8d 100644 --- a/compiler/testData/diagnostics/tests/inference/builderInference/kt53422.fir.kt +++ b/compiler/testData/diagnostics/tests/inference/builderInference/kt53422.fir.kt @@ -3,7 +3,7 @@ fun test() { foo( flow { emit(0) } - ) { it.collect {} } + ) { it.collect {} } // 0. Initial // W <: Any / declared upper bound @@ -53,4 +53,4 @@ class FlowCollectorImpl : FlowCollector { override fun emit(value: C) {} } -fun Flow<*>.collect() {} \ No newline at end of file +fun Flow<*>.collect() {} diff --git a/compiler/testData/diagnostics/tests/inference/builderInference/kt53639.fir.kt b/compiler/testData/diagnostics/tests/inference/builderInference/kt53639.fir.kt index ece7065f880..25652802ba4 100644 --- a/compiler/testData/diagnostics/tests/inference/builderInference/kt53639.fir.kt +++ b/compiler/testData/diagnostics/tests/inference/builderInference/kt53639.fir.kt @@ -12,7 +12,7 @@ data class Output(val source: InputWrapper>) fun main2(input: InputWrapper): Output { val output = input.doMapping( foo = { buildList { add("this is List") } }, - bar = { it.isNotEmpty() }, + bar = { it.isNotEmpty() }, ) return Output(source = output) diff --git a/compiler/testData/diagnostics/tests/inference/builderInference/multiLambdaRestriction.fir.kt b/compiler/testData/diagnostics/tests/inference/builderInference/multiLambdaRestriction.fir.kt index 64de8362a14..7f606cad70f 100644 --- a/compiler/testData/diagnostics/tests/inference/builderInference/multiLambdaRestriction.fir.kt +++ b/compiler/testData/diagnostics/tests/inference/builderInference/multiLambdaRestriction.fir.kt @@ -9,9 +9,9 @@ fun a(first: R, second: (List) -> Unit) {} fun test1() { a( buildList { add("") }, - second = { + second = { it.myGenericExt() - } + } ) } @@ -23,9 +23,9 @@ fun test2() { first = { buildList { add("") } }, - second = { + second = { it.myExt() // Note: must be extension to add constraints - } + } ) } @@ -45,13 +45,13 @@ fun test3() { ) select ( - myBuildList { add("") }, - myBuildList { add(1) }, + myBuildList { add("") }, + myBuildList { add(1) }, ) select ( - run { myBuildList { add("") } }, - myBuildList { add(1) }, + run { myBuildList { add("") } }, + myBuildList { add(1) }, ) } @@ -64,7 +64,7 @@ fun buildPartList(left: MutableList.() -> Unit, right: MutableList.() fun test4() { buildPartList( - left = { add(1) }, - right = { add("") } + left = { add(1) }, + right = { add("") } ) -} \ No newline at end of file +} diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java index 2fff4934c09..eb7173e1cd2 100644 --- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java +++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java @@ -16733,6 +16733,12 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest { KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/tests/inference/builderInference"), Pattern.compile("^(.*)\\.kts?$"), Pattern.compile("^(.+)\\.(reversed|fir|ll)\\.kts?$"), true); } + @Test + @TestMetadata("builderInferenceAnnotationInLambdaWithTVExpectedType.kt") + public void testBuilderInferenceAnnotationInLambdaWithTVExpectedType() throws Exception { + runTest("compiler/testData/diagnostics/tests/inference/builderInference/builderInferenceAnnotationInLambdaWithTVExpectedType.kt"); + } + @Test @TestMetadata("changingResolveIfDontUseBuilderInference.kt") public void testChangingResolveIfDontUseBuilderInference() throws Exception {