From 526ba6f92d37c7e5489897a3c0587b53336df74a Mon Sep 17 00:00:00 2001 From: Eric Milles Date: Sun, 26 Dec 2021 19:14:54 -0600 Subject: [PATCH] GROOVY-6603 --- .../tests/search/ClosureInferencingTests.java | 6 +- .../core/tests/xform/TypeCheckedTests.java | 62 ++++++++++++++++++- .../stc/StaticTypeCheckingVisitor.java | 12 ++++ .../stc/StaticTypeCheckingVisitor.java | 12 ++++ .../stc/StaticTypeCheckingVisitor.java | 15 ++++- .../tests/ContentAssistLocationTests.groovy | 4 +- 6 files changed, 104 insertions(+), 7 deletions(-) diff --git a/base-test/org.eclipse.jdt.groovy.core.tests.builder/src/org/eclipse/jdt/core/groovy/tests/search/ClosureInferencingTests.java b/base-test/org.eclipse.jdt.groovy.core.tests.builder/src/org/eclipse/jdt/core/groovy/tests/search/ClosureInferencingTests.java index 5be766f386..d13cb54830 100644 --- a/base-test/org.eclipse.jdt.groovy.core.tests.builder/src/org/eclipse/jdt/core/groovy/tests/search/ClosureInferencingTests.java +++ b/base-test/org.eclipse.jdt.groovy.core.tests.builder/src/org/eclipse/jdt/core/groovy/tests/search/ClosureInferencingTests.java @@ -1394,7 +1394,7 @@ public void testClosureParamsAnnotation1() { //@formatter:off String contents = "import groovy.transform.stc.*\n" + - "def match(@ClosureParams(value=SimpleType, options=['java.util.regex.Pattern']) Closure block) {\n" + + "def match(@ClosureParams(value=SimpleType, options='java.util.regex.Pattern') Closure block) {\n" + " block(item)\n" + "}\n" + "\n" + @@ -1426,7 +1426,7 @@ public void testClosureParamsAnnotation3() { String contents = "import groovy.transform.stc.*\n" + "class C {\n" + - " C(String s, @ClosureParams(value=SimpleType, options='java.util.List') Closure c) {\n" + + " C(String s, @ClosureParams(value=FromString, options='java.util.List') Closure c) {\n" + " }\n" + "}\n" + "new C('str', { list -> null })\n"; @@ -1440,7 +1440,7 @@ public void testClosureParamsAnnotation4() { String contents = "import groovy.transform.stc.*\n" + "class C {\n" + - " static m(String s, @ClosureParams(value=SimpleType, options='java.util.List') Closure c) {\n" + + " static m(String s, @ClosureParams(value=FromString, options='java.util.List') Closure c) {\n" + " }\n" + " static test() {\n" + " m('str', { list -> null })\n" + diff --git a/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/TypeCheckedTests.java b/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/TypeCheckedTests.java index e4724e6cc0..281982c4bc 100644 --- a/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/TypeCheckedTests.java +++ b/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/TypeCheckedTests.java @@ -170,7 +170,7 @@ public void testTypeChecked7() { "Main.groovy", "import groovy.transform.stc.*\n" + "class C {\n" + - " C(String s, @ClosureParams(value=SimpleType, options='java.util.List') Closure c) {\n" + + " C(String s, @ClosureParams(value=FromString,options='java.util.List') Closure c) {\n" + " }\n" + "}\n" + "@groovy.transform.TypeChecked\n" + @@ -633,6 +633,66 @@ public void testTypeChecked6455() { runConformTest(sources); } + @Test + public void testTypeChecked6603() { + //@formatter:off + String[] sources = { + "Main.groovy", + "import groovy.transform.stc.*\n" + + "@groovy.transform.TypeChecked\n" + + "void test(@ClosureParams(value=FromString,options='java.lang.Number') Closure c) {\n" + + " c('x')\n" + + "}\n", + }; + //@formatter:on + + runNegativeTest(sources, + "----------\n" + + "1. ERROR in Main.groovy (at line 4)\n" + + "\tc('x')\n" + + "\t ^^^\n" + + "Groovy:[Static type checking] - Cannot call closure that accepts [java.lang.Number] with [java.lang.String]\n" + + "----------\n"); + } + + @Test + public void testTypeChecked6603a() { + //@formatter:off + String[] sources = { + "Main.groovy", + "import groovy.transform.stc.*\n" + + "@groovy.transform.TypeChecked\n" + + "void test(@ClosureParams(value=FromString,options='java.util.List') Closure c) {\n" + + " c(['x'])\n" + + "}\n", + }; + //@formatter:on + + runNegativeTest(sources, + "----------\n" + + "1. ERROR in Main.groovy (at line 4)\n" + + "\tc(['x'])\n" + + "\t ^^^^^\n" + + "Groovy:[Static type checking] - Cannot call closure that accepts [java.util.List] with [java.util.List]\n" + + "----------\n"); + } + + @Test + public void testTypeChecked6603b() { + //@formatter:off + String[] sources = { + "Main.groovy", + "import groovy.transform.stc.*\n" + + "@groovy.transform.TypeChecked\n" + + "void test(@ClosureParams(value=FromString,options='java.util.Collection') Closure c) {\n" + + " c(Collections.singletonList('x'))\n" + + "}\n", + }; + //@formatter:on + + runNegativeTest(sources, ""); + } + @Test public void testTypeChecked6731() { //@formatter:off diff --git a/base/org.codehaus.groovy25/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java b/base/org.codehaus.groovy25/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java index 4c6cfd951a..0f4a81a751 100644 --- a/base/org.codehaus.groovy25/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java +++ b/base/org.codehaus.groovy25/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java @@ -2696,6 +2696,18 @@ private void negativeOrPositiveUnary(Expression expression, String name) { protected void visitConstructorOrMethod(MethodNode node, boolean isConstructor) { typeCheckingContext.pushEnclosingMethod(node); if (!isSkipMode(node) && !shouldSkipMethodNode(node)) { + // GRECLIPSE add -- GROOVY-6603 + for (Parameter parameter : node.getParameters()) { + for (AnnotationNode annotation : parameter.getAnnotations()) { + if (annotation.getClassNode().equals(CLOSUREPARAMS_CLASSNODE)) { + List signatures = getSignaturesFromHint(null, node, annotation.getMember("value"), annotation.getMember("options")); + if (signatures.size() == 1) { + parameter.putNodeMetaData(StaticTypesMarker.CLOSURE_ARGUMENTS, Arrays.stream(signatures.get(0)).map(t -> new Parameter(t,"")).toArray(Parameter[]::new)); + } + } + } + } + // GRECLIPSE end super.visitConstructorOrMethod(node, isConstructor); // GRECLIPSE add if (node.hasDefaultValue()) diff --git a/base/org.codehaus.groovy30/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java b/base/org.codehaus.groovy30/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java index 6ad1c2cfbd..84768bf143 100644 --- a/base/org.codehaus.groovy30/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java +++ b/base/org.codehaus.groovy30/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java @@ -2454,6 +2454,18 @@ private void negativeOrPositiveUnary(final Expression expression, final String n protected void visitConstructorOrMethod(final MethodNode node, final boolean isConstructor) { typeCheckingContext.pushEnclosingMethod(node); if (!isSkipMode(node) && !shouldSkipMethodNode(node)) { + // GRECLIPSE add -- GROOVY-6603 + for (Parameter parameter : node.getParameters()) { + for (AnnotationNode annotation : parameter.getAnnotations()) { + if (annotation.getClassNode().equals(CLOSUREPARAMS_CLASSNODE)) { + List signatures = getSignaturesFromHint(null, node, annotation.getMember("value"), annotation.getMember("options")); + if (signatures.size() == 1) { + parameter.putNodeMetaData(CLOSURE_ARGUMENTS, Arrays.stream(signatures.get(0)).map(t -> new Parameter(t,"")).toArray(Parameter[]::new)); + } + } + } + } + // GRECLIPSE end super.visitConstructorOrMethod(node, isConstructor); // GRECLIPSE add if (node.hasDefaultValue()) diff --git a/base/org.codehaus.groovy40/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java b/base/org.codehaus.groovy40/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java index 1ca658a801..a95faffa4c 100644 --- a/base/org.codehaus.groovy40/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java +++ b/base/org.codehaus.groovy40/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java @@ -2595,6 +2595,19 @@ protected void startMethodInference(final MethodNode node, final ErrorCollector @Override protected void visitConstructorOrMethod(final MethodNode node, final boolean isConstructor) { typeCheckingContext.pushEnclosingMethod(node); + for (Parameter parameter : node.getParameters()) { + for (AnnotationNode annotation : parameter.getAnnotations()) { + if (annotation.getClassNode().equals(CLOSUREPARAMS_CLASSNODE)) { + // GROOVY-6603: propagate closure parameter types + Expression value = annotation.getMember("value"); + Expression options = annotation.getMember("options"); + List signatures = getSignaturesFromHint(null, node, value, options); + if (signatures.size() == 1) { + parameter.putNodeMetaData(CLOSURE_ARGUMENTS, Arrays.stream(signatures.get(0)).map(t -> new Parameter(t,"")).toArray(Parameter[]::new)); + } + } + } + } super.visitConstructorOrMethod(node, isConstructor); if (node.hasDefaultValue()) { for (Parameter parameter : node.getParameters()) { @@ -3366,7 +3379,7 @@ public void visitMethodCallExpression(final MethodCallExpression call) { if (parameters != null) { typeCheckClosureCall(callArguments, args, parameters); } - ClassNode type = getType(((ASTNode) variable)); + ClassNode type = getType((ASTNode) variable); if (type.equals(CLOSURE_TYPE)) { // GROOVY-10098, et al. GenericsType[] genericsTypes = type.getGenericsTypes(); if (genericsTypes != null && genericsTypes.length == 1 diff --git a/ide-test/org.codehaus.groovy.eclipse.codeassist.test/src/org/codehaus/groovy/eclipse/codeassist/tests/ContentAssistLocationTests.groovy b/ide-test/org.codehaus.groovy.eclipse.codeassist.test/src/org/codehaus/groovy/eclipse/codeassist/tests/ContentAssistLocationTests.groovy index 0b40ccec11..033c3fe584 100644 --- a/ide-test/org.codehaus.groovy.eclipse.codeassist.test/src/org/codehaus/groovy/eclipse/codeassist/tests/ContentAssistLocationTests.groovy +++ b/ide-test/org.codehaus.groovy.eclipse.codeassist.test/src/org/codehaus/groovy/eclipse/codeassist/tests/ContentAssistLocationTests.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2009-2020 the original author or authors. + * Copyright 2009-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -1452,7 +1452,7 @@ final class ContentAssistLocationTests extends CompletionTestSuite { //-------------------------------------------------------------------------- - private void assertLocation(String contents, ContentAssistLocation expected, @ClosureParams(value=SimpleType, options=['org.codehaus.groovy.eclipse.codeassist.requestor.ContentAssistContext']) @DelegatesTo(value=ContentAssistContext, strategy=Closure.DELEGATE_FIRST) Closure withContext = null) { + private void assertLocation(String contents, ContentAssistLocation expected, @ClosureParams(value=SimpleType, options='org.codehaus.groovy.eclipse.codeassist.requestor.ContentAssistContext') @DelegatesTo(value=ContentAssistContext, strategy=Closure.DELEGATE_FIRST) Closure withContext = null) { def unit = addGroovySource(contents.replace('#', ''), nextUnitName()), offset = contents.indexOf('#') def context = new GroovyCompletionProposalComputer().createContentAssistContext(unit, offset, new Document(unit.buffer.contents))