Update diagnostics for trailing lambdas, add quickfix

Alternative message for errors, caused by unexpected lambda expression arguments on a new line.
Both diagnostic are reported, if multiple lambda expressions were passed to the call.
For other errors trailing lambda diagnostic overrides the original one.

Quickfix for erroneous trailing lambdas on a new line after call.
Fix separates lambda expression from previous call with semicolon.
All trailing lambda arguments become standalone lambda expressions.
This commit is contained in:
Pavel Kirpichenkov
2019-09-18 12:04:42 +03:00
parent f00d609459
commit 6c8e829f19
21 changed files with 390 additions and 9 deletions
@@ -695,6 +695,7 @@ public interface Errors {
DiagnosticFactory0<LeafPsiElement> SPREAD_OF_LAMBDA_OR_CALLABLE_REFERENCE = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<KtExpression> MANY_LAMBDA_EXPRESSION_ARGUMENTS = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<KtLambdaExpression> UNEXPECTED_TRAILING_LAMBDA_ON_A_NEW_LINE = DiagnosticFactory0.create(ERROR);
DiagnosticFactory1<PsiElement, CallableDescriptor> TOO_MANY_ARGUMENTS = DiagnosticFactory1.create(ERROR);
@@ -203,6 +203,7 @@ public class DefaultErrorMessages {
MAP.put(SPREAD_OF_LAMBDA_OR_CALLABLE_REFERENCE, "The spread operator (*foo) cannot be applied to lambda argument or callable reference");
MAP.put(MANY_LAMBDA_EXPRESSION_ARGUMENTS, "Only one lambda expression is allowed outside a parenthesized argument list");
MAP.put(UNEXPECTED_TRAILING_LAMBDA_ON_A_NEW_LINE, "Expression is treated a trailing lambda argument; consider separating it from call with semicolon");
MAP.put(PROPERTY_WITH_NO_TYPE_NO_INITIALIZER, "This property must either have a type annotation, be initialized or be delegated");
MAP.put(VARIABLE_WITH_NO_TYPE_NO_INITIALIZER, "This variable must either have a type annotation or be initialized");
@@ -18,6 +18,7 @@ import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.isNull
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.calls.callUtil.reportTrailingLambdaErrorOr
import org.jetbrains.kotlin.resolve.calls.context.BasicCallResolutionContext
import org.jetbrains.kotlin.resolve.calls.inference.model.*
import org.jetbrains.kotlin.resolve.calls.model.*
@@ -118,14 +119,15 @@ class DiagnosticReporterByTrackingStrategy(
SmartCastDiagnostic::class.java -> reportSmartCast(diagnostic as SmartCastDiagnostic)
UnstableSmartCast::class.java -> reportUnstableSmartCast(diagnostic as UnstableSmartCast)
TooManyArguments::class.java -> {
reportIfNonNull(callArgument.psiExpression) {
trace.report(TOO_MANY_ARGUMENTS.on(it, (diagnostic as TooManyArguments).descriptor))
trace.reportTrailingLambdaErrorOr(callArgument.psiExpression) { expr ->
TOO_MANY_ARGUMENTS.on(expr, (diagnostic as TooManyArguments).descriptor)
}
trace.markAsReported()
}
VarargArgumentOutsideParentheses::class.java ->
reportIfNonNull(callArgument.psiExpression) { trace.report(VARARG_OUTSIDE_PARENTHESES.on(it)) }
VarargArgumentOutsideParentheses::class.java -> trace.reportTrailingLambdaErrorOr(callArgument.psiExpression) { expr ->
VARARG_OUTSIDE_PARENTHESES.on(expr)
}
MixingNamedAndPositionArguments::class.java ->
trace.report(MIXING_NAMED_AND_POSITIONED_ARGUMENTS.on(callArgument.psiCallArgument.valueArgument.asElement()))
@@ -278,17 +278,26 @@ public class ValueArgumentsToParametersMapper {
KtExpression possiblyLabeledFunctionLiteral = lambdaArgument.getArgumentExpression();
if (parameters.isEmpty()) {
report(TOO_MANY_ARGUMENTS.on(possiblyLabeledFunctionLiteral, candidateCall.getCandidateDescriptor()));
CallUtilKt.reportTrailingLambdaErrorOr(
candidateCall.getTrace(), possiblyLabeledFunctionLiteral,
expression -> TOO_MANY_ARGUMENTS.on(expression, candidateCall.getCandidateDescriptor())
);
setStatus(ERROR);
}
else {
ValueParameterDescriptor lastParameter = CollectionsKt.last(parameters);
if (lastParameter.getVarargElementType() != null) {
report(VARARG_OUTSIDE_PARENTHESES.on(possiblyLabeledFunctionLiteral));
CallUtilKt.reportTrailingLambdaErrorOr(
candidateCall.getTrace(), possiblyLabeledFunctionLiteral,
expression -> VARARG_OUTSIDE_PARENTHESES.on(expression)
);
setStatus(ERROR);
}
else if (!usedParameters.add(lastParameter)) {
report(TOO_MANY_ARGUMENTS.on(possiblyLabeledFunctionLiteral, candidateCall.getCandidateDescriptor()));
CallUtilKt.reportTrailingLambdaErrorOr(
candidateCall.getTrace(), possiblyLabeledFunctionLiteral,
expr -> TOO_MANY_ARGUMENTS.on(expr, candidateCall.getCandidateDescriptor())
);
setStatus(WEAK_ERROR);
}
else {
@@ -298,7 +307,12 @@ public class ValueArgumentsToParametersMapper {
for (int i = 1; i < functionLiteralArguments.size(); i++) {
KtExpression argument = functionLiteralArguments.get(i).getArgumentExpression();
report(MANY_LAMBDA_EXPRESSION_ARGUMENTS.on(argument));
if (argument instanceof KtLambdaExpression) {
report(MANY_LAMBDA_EXPRESSION_ARGUMENTS.on(argument));
if (CallUtilKt.isTrailingLambdaOnNewLIne((KtLambdaExpression) argument)) {
report(UNEXPECTED_TRAILING_LAMBDA_ON_A_NEW_LINE.on((KtLambdaExpression) argument));
}
}
setStatus(WEAK_ERROR);
}
}
@@ -538,6 +538,9 @@ class PSICallResolver(
if (i == 0) continue
val lambdaExpression = externalLambdaArguments[i].getLambdaExpression() ?: continue
if (lambdaExpression.isTrailingLambdaOnNewLIne) {
context.trace.report(Errors.UNEXPECTED_TRAILING_LAMBDA_ON_A_NEW_LINE.on(lambdaExpression))
}
context.trace.report(Errors.MANY_LAMBDA_EXPRESSION_ARGUMENTS.on(lambdaExpression))
}
}
@@ -17,14 +17,18 @@
package org.jetbrains.kotlin.resolve.calls.callUtil
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiWhiteSpace
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.incremental.KotlinLookupLocation
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getTextWithLocation
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.BindingContext.CALL
import org.jetbrains.kotlin.resolve.BindingContext.RESOLVED_CALL
import org.jetbrains.kotlin.resolve.BindingTrace
import org.jetbrains.kotlin.resolve.calls.ArgumentTypeResolver
import org.jetbrains.kotlin.resolve.calls.CallTransformer
import org.jetbrains.kotlin.resolve.calls.context.ResolutionContext
@@ -278,4 +282,33 @@ fun ResolvedCall<*>.getFirstArgumentExpression(): KtExpression? =
valueArgumentsByIndex?.run { get(0).arguments[0].getArgumentExpression() }
fun ResolvedCall<*>.getReceiverExpression(): KtExpression? =
extensionReceiver.safeAs<ExpressionReceiver>()?.expression ?: dispatchReceiver.safeAs<ExpressionReceiver>()?.expression
extensionReceiver.safeAs<ExpressionReceiver>()?.expression ?: dispatchReceiver.safeAs<ExpressionReceiver>()?.expression
val KtLambdaExpression.isTrailingLambdaOnNewLIne
get(): Boolean {
parent?.safeAs<KtLambdaArgument>()?.let { lambdaArgument ->
var prevSibling = lambdaArgument.prevSibling
while (prevSibling != null && prevSibling !is KtElement) {
if (prevSibling is PsiWhiteSpace && prevSibling.textContains('\n'))
return true
prevSibling = prevSibling.prevSibling
}
}
return false
}
inline fun BindingTrace.reportTrailingLambdaErrorOr(
expression: KtExpression?,
originalDiagnostic: (KtExpression) -> Diagnostic
) {
expression?.let { expr ->
if (expr is KtLambdaExpression && expr.isTrailingLambdaOnNewLIne) {
report(Errors.UNEXPECTED_TRAILING_LAMBDA_ON_A_NEW_LINE.on(expr))
} else {
report(originalDiagnostic(expr))
}
}
}