diff --git a/base-test/org.eclipse.jdt.groovy.core.tests.builder/src/org/eclipse/jdt/core/groovy/tests/search/InferencingTests.java b/base-test/org.eclipse.jdt.groovy.core.tests.builder/src/org/eclipse/jdt/core/groovy/tests/search/InferencingTests.java index 5a636328d9..6d612df252 100644 --- a/base-test/org.eclipse.jdt.groovy.core.tests.builder/src/org/eclipse/jdt/core/groovy/tests/search/InferencingTests.java +++ b/base-test/org.eclipse.jdt.groovy.core.tests.builder/src/org/eclipse/jdt/core/groovy/tests/search/InferencingTests.java @@ -2395,7 +2395,7 @@ public void testCatchBlock6() { String contents = "try {\n} catch (Exception | Error e) {\n}\n"; int offset = contents.lastIndexOf("e"); - assertType(contents, offset, offset + 1, "java.lang.Throwable"); + assertType(contents, offset, offset + 1, "java.lang.Exception or java.lang.Error"); } private static final String CONTENTS_GETAT1 = diff --git a/base/org.eclipse.jdt.groovy.core/src/org/eclipse/jdt/groovy/core/util/GroovyUtils.java b/base/org.eclipse.jdt.groovy.core/src/org/eclipse/jdt/groovy/core/util/GroovyUtils.java index 7835055785..16ec82ca31 100644 --- a/base/org.eclipse.jdt.groovy.core/src/org/eclipse/jdt/groovy/core/util/GroovyUtils.java +++ b/base/org.eclipse.jdt.groovy.core/src/org/eclipse/jdt/groovy/core/util/GroovyUtils.java @@ -219,13 +219,17 @@ public static ClassNode[] getTypeParameterBounds(ClassNode typeParam) { * @see org.eclipse.jdt.core.Signature */ public static String getTypeSignature(ClassNode node, boolean qualified, boolean resolved) { - if (getBaseType(node).getName().startsWith(" getTypeSignature(type, qualified, resolved)); } + if (baseType instanceof IntersectionType) { + return getIntersectionTypeSignature(node, type -> getTypeSignature(type, qualified, resolved)); + } String signature = getTypeSignatureWithoutGenerics(node, qualified, resolved); - if (getBaseType(node).getGenericsTypes() != null && !getBaseType(node).isGenericsPlaceHolder()) { + if (baseType.getGenericsTypes() != null && !baseType.isGenericsPlaceHolder()) { GenericsType[] generics = getGenericsTypes(node); if (generics.length > 0) { StringBuilder builder = new StringBuilder(signature); @@ -253,9 +257,13 @@ public static String getTypeSignature(ClassNode node, boolean qualified, boolean } public static String getTypeSignatureWithoutGenerics(ClassNode node, boolean qualified, boolean resolved) { - if (getBaseType(node).getName().startsWith(" getTypeSignatureWithoutGenerics(type, qualified, resolved)); } + if (baseType instanceof IntersectionType) { + return getIntersectionTypeSignature(node, type -> getTypeSignatureWithoutGenerics(type, qualified, resolved)); + } StringBuilder builder = new StringBuilder(); while (node.isArray()) { @@ -304,6 +312,19 @@ private static String getUnionTypeSignature(ClassNode node, java.util.function.F return builder.append(signature).toString(); } + private static String getIntersectionTypeSignature(ClassNode node, java.util.function.Function signer) { + StringBuilder builder = new StringBuilder(); + while (node.isArray()) { + builder.append('['); + node = node.getComponentType(); + } + + Stream types = ((IntersectionType) node).types.stream(); + String signature = Signature.createIntersectionTypeSignature(types.map(signer).toArray(String[]::new)); + + return builder.append(signature).toString(); + } + public static ClassNode getWrapperTypeIfPrimitive(ClassNode type) { if (type != null && ClassHelper.isPrimitiveType(type) && !ClassHelper.VOID_TYPE.equals(type)) { return ClassHelper.getWrapper(type); @@ -402,6 +423,24 @@ public static Expression getTraitFieldExpression(MethodCallExpression call) { return null; } + public static boolean implementsTrait(ClassNode concreteType) { + return concreteType.getNodeMetaData(Traits.class, x -> { + ClassNode type = concreteType.redirect(); + do { + if (Traits.isTrait(type) || Arrays.stream(type.getInterfaces()).anyMatch(Traits::isTrait)) { + return Boolean.TRUE; + } + type = type.getSuperClass(); + } while (type != null); + return Boolean.FALSE; + }).booleanValue(); + } + + public static ClassNode intersect(List types) { + assert types != null && types.size() > 1; + return new IntersectionType(types); + } + public static boolean isAnonymous(ClassNode node) { if (node instanceof InnerClassNode) { return ((InnerClassNode) node).isAnonymous(); @@ -512,19 +551,6 @@ public static boolean isUsingGenerics(MethodNode node) { return false; } - public static boolean implementsTrait(ClassNode concreteType) { - return concreteType.getNodeMetaData(Traits.class, x -> { - ClassNode type = concreteType.redirect(); - do { - if (Traits.isTrait(type) || Arrays.stream(type.getInterfaces()).anyMatch(Traits::isTrait)) { - return Boolean.TRUE; - } - type = type.getSuperClass(); - } while (type != null); - return Boolean.FALSE; - }).booleanValue(); - } - public static ClassNode makeType(String typeNameWithoutGenerics) { int i = typeNameWithoutGenerics.lastIndexOf('['); if (i < 0) { @@ -533,10 +559,9 @@ public static ClassNode makeType(String typeNameWithoutGenerics) { return makeType(typeNameWithoutGenerics.substring(0, i)).makeArray(); } - public static void updateClosureWithInferredTypes(ClassNode closure, ClassNode returnType, Parameter[] parameters) { - if (!"groovy.lang.Closure".equals(closure.getName()) || closure == closure.redirect()) { - return; + public static void updateClosureWithInferredTypes(ClassNode closureType, ClassNode returnType, Parameter[] parameters) { + if (closureType.getName().equals("groovy.lang.Closure") && closureType.isRedirectNode()) { + closureType.setGenericsTypes(new GenericsType[] {getWrapperTypeIfPrimitive(returnType).asGenericsType()}); } - closure.setGenericsTypes(new GenericsType[] {new GenericsType(getWrapperTypeIfPrimitive(returnType))}); } } diff --git a/base/org.eclipse.jdt.groovy.core/src/org/eclipse/jdt/groovy/core/util/IntersectionType.java b/base/org.eclipse.jdt.groovy.core/src/org/eclipse/jdt/groovy/core/util/IntersectionType.java new file mode 100644 index 0000000000..93d616be89 --- /dev/null +++ b/base/org.eclipse.jdt.groovy.core/src/org/eclipse/jdt/groovy/core/util/IntersectionType.java @@ -0,0 +1,354 @@ +/* + * Copyright 2009-2022 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. + * You may obtain a copy of the License at + * + * https://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.eclipse.jdt.groovy.core.util; + +import static org.codehaus.groovy.ast.tools.WideningCategories.lowestUpperBound; + +import java.util.Collections; +import java.util.List; +import java.util.StringJoiner; + +import org.codehaus.groovy.ast.ASTNode; +import org.codehaus.groovy.ast.AnnotationNode; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.CompileUnit; +import org.codehaus.groovy.ast.ConstructorNode; +import org.codehaus.groovy.ast.FieldNode; +import org.codehaus.groovy.ast.GenericsType; +import org.codehaus.groovy.ast.MethodNode; +import org.codehaus.groovy.ast.MixinNode; +import org.codehaus.groovy.ast.ModuleNode; +import org.codehaus.groovy.ast.PropertyNode; +import org.codehaus.groovy.ast.stmt.Statement; +import org.codehaus.groovy.transform.ASTTransformation; + +class IntersectionType extends ClassNode { + + final List types; + + IntersectionType(List types) { + super(string(types), 0, lowestUpperBound(types)); + this.types = types; + } + + private static String string(List types) { + StringJoiner sj = new StringJoiner(" or ", "(", ")"); + for (ClassNode cn: types) sj.add(cn.toString(false)); + return sj.toString(); + } + + // + + @Override + public void lazyClassInit() { + } + + @Override + public String getPackageName() { + return null; + } + + @Override + public boolean hasPackageName() { + return false; + } + + @Override + public String getNameWithoutPackage() { + return getName(); + } + + @Override + public ClassNode getPlainNodeReference() { + throw new UnsupportedOperationException(); + } + + public ClassNode getPlainNodeReference(boolean b) { + throw new UnsupportedOperationException(); + } + + // + + @Override + public List getFields() { + return Collections.emptyList(); + } + + @Override + public List getMethods() { + return Collections.emptyList(); + } + + @Override + public List getProperties() { + return Collections.emptyList(); + } + + @Override + public List getDeclaredConstructors() { + return Collections.emptyList(); + } + + @Override + public List getObjectInitializerStatements() { + return Collections.emptyList(); + } + + //-------------------------------------------------------------------------- + + // ASTNode overrides: + + @Override + public void setColumnNumber(int n) { + throw new UnsupportedOperationException(); + } + + @Override + public void setLastColumnNumber(int n) { + throw new UnsupportedOperationException(); + } + + @Override + public void setLastLineNumber(int n) { + throw new UnsupportedOperationException(); + } + + @Override + public void setLineNumber(int n) { + throw new UnsupportedOperationException(); + } + + @Override + public Object putNodeMetaData(Object k, Object v) { + throw new UnsupportedOperationException(); + } + + @Override + public void setSourcePosition(ASTNode n) { + throw new UnsupportedOperationException(); + } + + @Override + public void setStart(int i) { + throw new UnsupportedOperationException(); + } + + @Override + public void setEnd(int i) { + throw new UnsupportedOperationException(); + } + + // AnnotatedNode overrides: + + @Override + public void setNameStart(int i) { + throw new UnsupportedOperationException(); + } + + @Override + public void setNameEnd(int i) { + throw new UnsupportedOperationException(); + } + + @Override + public void setDeclaringClass(ClassNode cn) { + throw new UnsupportedOperationException(); + } + + @Override + public void setHasNoRealSourcePosition(boolean b) { + throw new UnsupportedOperationException(); + } + + @Override + public void setSynthetic(boolean b) { + throw new UnsupportedOperationException(); + } + + // ClassNode overrides: + + @Override + public void setAnnotated(boolean b) { + throw new UnsupportedOperationException(); + } + + @Override + protected void setCompileUnit(CompileUnit cu) { + throw new UnsupportedOperationException(); + } + + @Override + public void setEnclosingMethod(MethodNode mn) { + throw new UnsupportedOperationException(); + } + + @Override + public void setGenericsPlaceHolder(boolean b) { + throw new UnsupportedOperationException(); + } + + @Override + public void setGenericsTypes(GenericsType[] gt) { + throw new UnsupportedOperationException(); + } + + @Override + public void setHasInconsistentHierarchy(boolean b) { + throw new UnsupportedOperationException(); + } + + @Override + public void setInterfaces(ClassNode[] cn) { + throw new UnsupportedOperationException(); + } + + @Override + public void setModifiers(int bf) { + throw new UnsupportedOperationException(); + } + + @Override + public void setModule(ModuleNode mn) { + throw new UnsupportedOperationException(); + } + + @Override + public String setName(String s) { + throw new UnsupportedOperationException(); + } + + @Override + public void setNameStart2(int i) { + throw new UnsupportedOperationException(); + } + + public void setPermittedSubclasses(List ps) { + throw new UnsupportedOperationException(); + } + + /*public void setRecordComponents(List rc) { + throw new UnsupportedOperationException(); + }*/ + + @Override + public void setSuperClass(ClassNode cn) { + throw new UnsupportedOperationException(); + } + + @Override + public void setScript(boolean b) { + throw new UnsupportedOperationException(); + } + + @Override + public void setScriptBody(boolean b) { + throw new UnsupportedOperationException(); + } + + @Override + public void setStaticClass(boolean b) { + throw new UnsupportedOperationException(); + } + + @Override + public void setSyntheticPublic(boolean b) { + throw new UnsupportedOperationException(); + } + + @Override + public void setUnresolvedSuperClass(ClassNode cn) { + throw new UnsupportedOperationException(); + } + + @Override + public void setUsingGenerics(boolean b) { + throw new UnsupportedOperationException(); + } + + // + + @Override + public void addAnnotation(AnnotationNode node) { + throw new UnsupportedOperationException(); + } + + @Override + public void addConstructor(ConstructorNode node) { + throw new UnsupportedOperationException(); + } + + @Override + public void addField(FieldNode node) { + throw new UnsupportedOperationException(); + } + + @Override + public void addFieldFirst(FieldNode node) { + throw new UnsupportedOperationException(); + } + + @Override + public void addMethod(MethodNode node) { + throw new UnsupportedOperationException(); + } + + @Override + public void addMixin(MixinNode node) { + throw new UnsupportedOperationException(); + } + + @Override + public void addProperty(PropertyNode node) { + throw new UnsupportedOperationException(); + } + + @Override + public void addStaticInitializerStatements(List list, boolean init) { + throw new UnsupportedOperationException(); + } + + @Override + public void addTransform(Class cls, ASTNode node) { + throw new UnsupportedOperationException(); + } + + @Override + public void addTypeAnnotations(List list) { + throw new UnsupportedOperationException(); + } + + // + + @Override + public void removeConstructor(ConstructorNode node) { + throw new UnsupportedOperationException(); + } + + @Override + public void removeMethod(MethodNode node) { + throw new UnsupportedOperationException(); + } + + @Override + public void removeField(String name) { + throw new UnsupportedOperationException(); + } + + @Override + public void renameField(String oldName, String newName) { + throw new UnsupportedOperationException(); + } +} diff --git a/base/org.eclipse.jdt.groovy.core/src/org/eclipse/jdt/groovy/search/TypeInferencingVisitorWithRequestor.java b/base/org.eclipse.jdt.groovy.core/src/org/eclipse/jdt/groovy/search/TypeInferencingVisitorWithRequestor.java index 762dbb8ea0..3244502234 100644 --- a/base/org.eclipse.jdt.groovy.core/src/org/eclipse/jdt/groovy/search/TypeInferencingVisitorWithRequestor.java +++ b/base/org.eclipse.jdt.groovy.core/src/org/eclipse/jdt/groovy/search/TypeInferencingVisitorWithRequestor.java @@ -914,8 +914,7 @@ public void visitCatchStatement(final CatchStatement node) { if (param != null) { // add param to scope, accounting for multi-catch List types = node.getNodeMetaData("catch.types"); - scopes.getLast().addVariable(param.getName(), types == null ? param.getType() - : WideningCategories.lowestUpperBound(types), null); + scopes.getLast().addVariable(param.getName(), types == null ? param.getType() : GroovyUtils.intersect(types), null); handleParameters(param); } super.visitCatchStatement(node); @@ -1992,8 +1991,7 @@ private boolean handleParameters(final Parameter... params) { } } - TypeLookupResult parameterResult = new TypeLookupResult(result.type, result.declaringType, param, TypeConfidence.EXACT, scope); - VisitStatus status = notifyRequestor(param, requestor, parameterResult); + VisitStatus status = notifyRequestor(param, requestor, result); switch (status) { case CONTINUE: break;