Skip to content

Commit

Permalink
GROOVY-11563: STC: check compound assignment x op= y like x = x op y
Browse files Browse the repository at this point in the history
  • Loading branch information
eric-milles committed Feb 2, 2025
1 parent 6dff05e commit 18c00c1
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 21 deletions.
5 changes: 3 additions & 2 deletions src/main/groovy/groovy/grape/GrapeIvy.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import org.codehaus.groovy.runtime.metaclass.MetaClassRegistryImpl
import org.w3c.dom.Element

import javax.xml.parsers.DocumentBuilderFactory

import java.text.ParseException
import java.util.jar.JarFile
import java.util.regex.Pattern
Expand Down Expand Up @@ -589,7 +590,7 @@ class GrapeIvy implements GrapeEngine {
List<String> versions = []
moduleDir.eachFileMatch(ivyFilePattern) { File ivyFile ->
def m = ivyFilePattern.matcher(ivyFile.getName())
if (m.matches()) versions += m.group(1)
if (m.matches()) versions.add(m.group(1))
}
grapes[moduleDir.getName()] = versions
}
Expand Down Expand Up @@ -656,7 +657,7 @@ class GrapeIvy implements GrapeEngine {
for (ArtifactDownloadReport adl : report.getAllArtifactsReports()) {
// TODO: check artifact type, jar vs library, etc.
if (adl.getLocalFile()) {
results += adl.getLocalFile().toURI()
results.add(adl.getLocalFile().toURI())
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,10 @@
*/
package org.codehaus.groovy.ast.expr;


/**
* Provides a way to transform expressions
* Provides a way to transform expressions.
*/
@FunctionalInterface
public interface ExpressionTransformer {

/**
* Transforms the given expression into another expression
*/
Expression transform(Expression expression);
}
Original file line number Diff line number Diff line change
Expand Up @@ -702,7 +702,7 @@ private void writeStringPlusCall(final Expression receiver, final String message
visitBoxedArgument(arguments);
int m2 = operandStack.getStackLength();
MethodVisitor mv = controller.getMethodVisitor();
mv.visitMethodInsn(INVOKESTATIC, "org/codehaus/groovy/runtime/DefaultGroovyMethods", "plus", "(Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/String;", false);
mv.visitMethodInsn(INVOKESTATIC, "org/codehaus/groovy/runtime/StringGroovyMethods", "plus", "(Ljava/lang/CharSequence;Ljava/lang/Object;)Ljava/lang/String;", false);
operandStack.replace(STRING_TYPE, m2 - m1);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
import org.codehaus.groovy.ast.expr.ElvisOperatorExpression;
import org.codehaus.groovy.ast.expr.EmptyExpression;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.expr.ExpressionTransformer;
import org.codehaus.groovy.ast.expr.FieldExpression;
import org.codehaus.groovy.ast.expr.LambdaExpression;
import org.codehaus.groovy.ast.expr.ListExpression;
Expand Down Expand Up @@ -271,6 +272,7 @@
import static org.codehaus.groovy.syntax.Types.PLUS_PLUS;
import static org.codehaus.groovy.syntax.Types.REMAINDER;
import static org.codehaus.groovy.syntax.Types.REMAINDER_EQUAL;
import static org.codehaus.groovy.transform.sc.StaticCompilationMetadataKeys.BINARY_EXP_TARGET;
import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.ArrayList_TYPE;
import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.Collection_TYPE;
import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.LinkedHashMap_TYPE;
Expand Down Expand Up @@ -815,15 +817,49 @@ public void visitBinaryExpression(final BinaryExpression expression) {
int op = expression.getOperation().getType();
Expression leftExpression = expression.getLeftExpression();
Expression rightExpression = expression.getRightExpression();
Expression wrongExpression = null; // GROOVY-10628, GROOVY-11563

if (isAssignment(op) && op != EQUAL) {
// for "x op= y", find type as if it was "x = x op y"
ExpressionTransformer cloneMaker = new ExpressionTransformer() {
@Override
public Expression transform(final Expression expr) {
// TODO: is there a beter way to clone?
if (expr instanceof VariableExpression) {
var variable = (VariableExpression) expr;
var copy = new VariableExpression(variable.getAccessedVariable());
copy.setClosureSharedVariable(variable.isClosureSharedVariable());
copy.setUseReferenceDirectly(variable.isUseReferenceDirectly());
copy.setInStaticContext(variable.isInStaticContext());
copy.setSynthetic(variable.isSynthetic());
copy.setType(variable.getType());
copy.setSourcePosition(variable);
copy.copyNodeMetaData(variable);
return copy;
}
return expr.transformExpression(this);
}
};
if (op == ELVIS_EQUAL) {
wrongExpression = elvisX(cloneMaker.transform(leftExpression), rightExpression);
} else {
wrongExpression = binX(cloneMaker.transform(leftExpression), Token.newSymbol(TokenUtil.removeAssignment(op), expression.getOperation().getStartLine(), expression.getOperation().getStartColumn()), rightExpression);
}
wrongExpression.setSourcePosition(expression);
op = EQUAL;
}

leftExpression.visit(this);
SetterInfo setterInfo = removeSetterInfo(leftExpression);
ClassNode lType = null;
// TODO: can visit and getType be split out of if/else for simpler structure?
if (setterInfo != null) {
if (ensureValidSetter(expression, leftExpression, rightExpression, setterInfo)) {
return;
}
lType = getType(leftExpression);
// TODO: ensureValidSetter expands compound operator and visits rightExpression
if (wrongExpression != null) wrongExpression.visit(this);
} else {
if (op != EQUAL && op != ELVIS_EQUAL) {
lType = getType(leftExpression);
Expand All @@ -832,7 +868,9 @@ public void visitBinaryExpression(final BinaryExpression expression) {

applyTargetType(lType, rightExpression);
}
rightExpression.visit(this);
// TODO: want to visit before left expression above...
if (wrongExpression != null) wrongExpression.visit(this);
else rightExpression.visit(this);
}

ClassNode rType = isNullConstant(rightExpression)
Expand All @@ -846,16 +884,12 @@ public void visitBinaryExpression(final BinaryExpression expression) {
resultType = getResultType(rType, op, lType, reverseExpression);
if (resultType == null) resultType = boolean_TYPE; // GROOVY-10239
storeTargetMethod(expression, reverseExpression.getNodeMetaData(DIRECT_METHOD_CALL_TARGET));
} else {
} else if (wrongExpression == null) {
resultType = getResultType(lType, op, rType, expression);
if (op == ELVIS_EQUAL) {
// TODO: Should this transform and visit be done before left and right are visited above?
Expression fullExpression = new ElvisOperatorExpression(leftExpression, rightExpression);
fullExpression.setSourcePosition(expression);
fullExpression.visit(this);

resultType = getType(fullExpression);
}
} else {
resultType = getResultType(lType, op, getType(wrongExpression), expression);
expression.putNodeMetaData(BINARY_EXP_TARGET, wrongExpression.getNodeMetaData(BINARY_EXP_TARGET));
expression.putNodeMetaData(DIRECT_METHOD_CALL_TARGET, wrongExpression.getNodeMetaData(DIRECT_METHOD_CALL_TARGET));
}
if (resultType == null) {
resultType = lType;
Expand Down
13 changes: 11 additions & 2 deletions src/test/groovy/transform/stc/STCAssignmentTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -341,10 +341,19 @@ class STCAssignmentTest extends StaticTypeCheckingTestCase {
'Cannot find matching method java.lang.Integer#minus(java.lang.Object)'
}

// GROOVY-11563
void testNumberPlusEqualsString() {
shouldFailWithMessages '''
Number n = 0
n += "no"
''',
'Cannot assign value of type java.lang.String to variable of type java.lang.Number'
}

void testStringPlusEqualsString() {
assertScript '''
String str = 'test'
str += 'test2'
String s = 'prefix'
s += 'suffix'
'''
}

Expand Down

0 comments on commit 18c00c1

Please sign in to comment.