From 28ff54e47cb60a4411d5bb3f5146622f844fa2bd Mon Sep 17 00:00:00 2001 From: Sebastian Zarnekow Date: Mon, 8 Jan 2024 10:00:45 +0100 Subject: [PATCH] Fixed type common super type computation when type params are involved Signed-off-by: Sebastian Zarnekow --- .../tests/compiler/CompilerBug2351Test.xtend | 161 ++++++++ .../tests/compiler/CompilerBug2351Test.java | 373 ++++++++++++++++++ .../conformance/TypeConformanceComputer.java | 36 +- 3 files changed, 568 insertions(+), 2 deletions(-) create mode 100644 org.eclipse.xtend.core.tests/src/org/eclipse/xtend/core/tests/compiler/CompilerBug2351Test.xtend create mode 100644 org.eclipse.xtend.core.tests/xtend-gen/org/eclipse/xtend/core/tests/compiler/CompilerBug2351Test.java diff --git a/org.eclipse.xtend.core.tests/src/org/eclipse/xtend/core/tests/compiler/CompilerBug2351Test.xtend b/org.eclipse.xtend.core.tests/src/org/eclipse/xtend/core/tests/compiler/CompilerBug2351Test.xtend new file mode 100644 index 00000000000..58fa672a382 --- /dev/null +++ b/org.eclipse.xtend.core.tests/src/org/eclipse/xtend/core/tests/compiler/CompilerBug2351Test.xtend @@ -0,0 +1,161 @@ +/******************************************************************************* + * Copyright (c) 2024 Sebastian Zarnekow (http://www.itemis.eu) and others. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.xtend.core.tests.compiler + +import org.junit.Test + +/** + * @author Sebastian - Initial contribution and API + */ +class CompilerBug2351Test extends AbstractXtendCompilerTest { + + @Test def void test_01() { + assertCompilesTo( + ''' + package p + + import java.math.BigDecimal + + class C { + def Object m(int i) { + val r = switch(i) { + case 1: 1.0 // double + case 2: 1.0bd // BigDecimal + default: weird("") + } + return r + } + + private def T weird(Object object) { + return null; + } + } + ''', + ''' + package p; + + import java.math.BigDecimal; + + @SuppressWarnings("all") + public class C { + public Object m(final int i) { + Object _switchResult = null; + switch (i) { + case 1: + _switchResult = Double.valueOf(1.0); + break; + case 2: + _switchResult = new BigDecimal("1.0"); + break; + default: + _switchResult = this.weird(""); + break; + } + final Object r = _switchResult; + return r; + } + + private T weird(final Object object) { + return null; + } + } + ''' + ) + } + + @Test def void test_02() { + assertCompilesTo( + ''' + package p + + import java.math.BigDecimal + + class C { + def void m() { + val s1 = switch 45 { // int + case 2: 1 + case 3: 3.0 + case 78: exit('.') + case 4: 4.0 / 5.0 + default: 6 + } + val s2 = switch 45 { // Number + case 78: exit('.') + case 2: 1 + case 3: 3.0 + case 4: 4.0/5.0 + default: 6 + } + s1.toString + s2.toString + } + + def T exit(String message) { + throw new UnsupportedOperationException(message) + } + } + ''', + ''' + package p; + + @SuppressWarnings("all") + public class C { + public void m() { + Number _switchResult = null; + final int _switchValue = 45; + switch (_switchValue) { + case 2: + _switchResult = Integer.valueOf(1); + break; + case 3: + _switchResult = Double.valueOf(3.0); + break; + case 78: + _switchResult = ((Number)this.exit(".")); + break; + case 4: + _switchResult = Double.valueOf((4.0 / 5.0)); + break; + default: + _switchResult = Integer.valueOf(6); + break; + } + final Number s1 = ((Number)_switchResult); + Number _switchResult_1 = null; + final int _switchValue_1 = 45; + switch (_switchValue_1) { + case 78: + _switchResult_1 = ((Number)this.exit(".")); + break; + case 2: + _switchResult_1 = Integer.valueOf(1); + break; + case 3: + _switchResult_1 = Double.valueOf(3.0); + break; + case 4: + _switchResult_1 = Double.valueOf((4.0 / 5.0)); + break; + default: + _switchResult_1 = Integer.valueOf(6); + break; + } + final Number s2 = ((Number)_switchResult_1); + s1.toString(); + s2.toString(); + } + + public T exit(final String message) { + throw new UnsupportedOperationException(message); + } + } + ''' + ) + } +} \ No newline at end of file diff --git a/org.eclipse.xtend.core.tests/xtend-gen/org/eclipse/xtend/core/tests/compiler/CompilerBug2351Test.java b/org.eclipse.xtend.core.tests/xtend-gen/org/eclipse/xtend/core/tests/compiler/CompilerBug2351Test.java new file mode 100644 index 00000000000..9c958374320 --- /dev/null +++ b/org.eclipse.xtend.core.tests/xtend-gen/org/eclipse/xtend/core/tests/compiler/CompilerBug2351Test.java @@ -0,0 +1,373 @@ +/** + * Copyright (c) 2024 Sebastian Zarnekow (http://www.itemis.eu) and others. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.xtend.core.tests.compiler; + +import org.eclipse.xtend2.lib.StringConcatenation; +import org.junit.Test; + +/** + * @author Sebastian - Initial contribution and API + */ +@SuppressWarnings("all") +public class CompilerBug2351Test extends AbstractXtendCompilerTest { + @Test + public void test_01() { + StringConcatenation _builder = new StringConcatenation(); + _builder.append("package p"); + _builder.newLine(); + _builder.newLine(); + _builder.append("import java.math.BigDecimal"); + _builder.newLine(); + _builder.newLine(); + _builder.append("class C {"); + _builder.newLine(); + _builder.append("\t"); + _builder.append("def Object m(int i) {"); + _builder.newLine(); + _builder.append("\t\t"); + _builder.append("val r = switch(i) {"); + _builder.newLine(); + _builder.append("\t\t\t"); + _builder.append("case 1: 1.0 // double"); + _builder.newLine(); + _builder.append("\t\t\t"); + _builder.append("case 2: 1.0bd // BigDecimal"); + _builder.newLine(); + _builder.append("\t\t\t"); + _builder.append("default: weird(\"\")"); + _builder.newLine(); + _builder.append("\t\t"); + _builder.append("}"); + _builder.newLine(); + _builder.append("\t\t"); + _builder.append("return r\t"); + _builder.newLine(); + _builder.append("\t"); + _builder.append("}"); + _builder.newLine(); + _builder.append("\t"); + _builder.newLine(); + _builder.append("\t"); + _builder.append("private def T weird(Object object) {"); + _builder.newLine(); + _builder.append("\t\t"); + _builder.append("return null;"); + _builder.newLine(); + _builder.append("\t"); + _builder.append("}"); + _builder.newLine(); + _builder.append("}"); + _builder.newLine(); + StringConcatenation _builder_1 = new StringConcatenation(); + _builder_1.append("package p;"); + _builder_1.newLine(); + _builder_1.newLine(); + _builder_1.append("import java.math.BigDecimal;"); + _builder_1.newLine(); + _builder_1.newLine(); + _builder_1.append("@SuppressWarnings(\"all\")"); + _builder_1.newLine(); + _builder_1.append("public class C {"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("public Object m(final int i) {"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("Object _switchResult = null;"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("switch (i) {"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("case 1:"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("_switchResult = Double.valueOf(1.0);"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("break;"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("case 2:"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("_switchResult = new BigDecimal(\"1.0\");"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("break;"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("default:"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("_switchResult = this.weird(\"\");"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("break;"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("}"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("final Object r = _switchResult;"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("return r;"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("}"); + _builder_1.newLine(); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("private T weird(final Object object) {"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("return null;"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("}"); + _builder_1.newLine(); + _builder_1.append("}"); + _builder_1.newLine(); + this.assertCompilesTo(_builder, _builder_1); + } + + @Test + public void test_02() { + StringConcatenation _builder = new StringConcatenation(); + _builder.append("package p"); + _builder.newLine(); + _builder.newLine(); + _builder.append("import java.math.BigDecimal"); + _builder.newLine(); + _builder.newLine(); + _builder.append("class C {"); + _builder.newLine(); + _builder.append("\t"); + _builder.append("def void m() {"); + _builder.newLine(); + _builder.append("\t\t\t"); + _builder.append("val s1 = switch 45 { // int"); + _builder.newLine(); + _builder.append("\t\t\t\t"); + _builder.append("case 2: 1"); + _builder.newLine(); + _builder.append("\t\t\t\t"); + _builder.append("case 3: 3.0"); + _builder.newLine(); + _builder.append("\t\t\t\t"); + _builder.append("case 78: exit(\'.\')"); + _builder.newLine(); + _builder.append("\t\t\t\t"); + _builder.append("case 4: 4.0 / 5.0"); + _builder.newLine(); + _builder.append("\t\t\t\t"); + _builder.append("default: 6"); + _builder.newLine(); + _builder.append("\t\t\t"); + _builder.append("}"); + _builder.newLine(); + _builder.append("\t\t\t"); + _builder.append("val s2 = switch 45 { // Number"); + _builder.newLine(); + _builder.append("\t\t\t "); + _builder.append("case 78: exit(\'.\')"); + _builder.newLine(); + _builder.append("\t\t\t "); + _builder.append("case 2: 1"); + _builder.newLine(); + _builder.append("\t\t\t "); + _builder.append("case 3: 3.0"); + _builder.newLine(); + _builder.append("\t\t\t "); + _builder.append("case 4: 4.0/5.0"); + _builder.newLine(); + _builder.append("\t\t\t "); + _builder.append("default: 6 "); + _builder.newLine(); + _builder.append("\t\t\t"); + _builder.append("} "); + _builder.newLine(); + _builder.append("\t\t\t"); + _builder.append("s1.toString"); + _builder.newLine(); + _builder.append("\t\t\t"); + _builder.append("s2.toString"); + _builder.newLine(); + _builder.append("\t\t"); + _builder.append("}"); + _builder.newLine(); + _builder.append("\t"); + _builder.newLine(); + _builder.append("\t\t"); + _builder.append("def T exit(String message) {"); + _builder.newLine(); + _builder.append("\t\t\t"); + _builder.append("throw new UnsupportedOperationException(message)"); + _builder.newLine(); + _builder.append("\t\t"); + _builder.append("}"); + _builder.newLine(); + _builder.append("}"); + _builder.newLine(); + StringConcatenation _builder_1 = new StringConcatenation(); + _builder_1.append("package p;"); + _builder_1.newLine(); + _builder_1.newLine(); + _builder_1.append("@SuppressWarnings(\"all\")"); + _builder_1.newLine(); + _builder_1.append("public class C {"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("public void m() {"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("Number _switchResult = null;"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("final int _switchValue = 45;"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("switch (_switchValue) {"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("case 2:"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("_switchResult = Integer.valueOf(1);"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("break;"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("case 3:"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("_switchResult = Double.valueOf(3.0);"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("break;"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("case 78:"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("_switchResult = ((Number)this.exit(\".\"));"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("break;"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("case 4:"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("_switchResult = Double.valueOf((4.0 / 5.0));"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("break;"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("default:"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("_switchResult = Integer.valueOf(6);"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("break;"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("}"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("final Number s1 = ((Number)_switchResult);"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("Number _switchResult_1 = null;"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("final int _switchValue_1 = 45;"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("switch (_switchValue_1) {"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("case 78:"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("_switchResult_1 = ((Number)this.exit(\".\"));"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("break;"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("case 2:"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("_switchResult_1 = Integer.valueOf(1);"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("break;"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("case 3:"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("_switchResult_1 = Double.valueOf(3.0);"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("break;"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("case 4:"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("_switchResult_1 = Double.valueOf((4.0 / 5.0));"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("break;"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("default:"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("_switchResult_1 = Integer.valueOf(6);"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("break;"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("}"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("final Number s2 = ((Number)_switchResult_1);"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("s1.toString();"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("s2.toString();"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("}"); + _builder_1.newLine(); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("public T exit(final String message) {"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("throw new UnsupportedOperationException(message);"); + _builder_1.newLine(); + _builder_1.append(" "); + _builder_1.append("}"); + _builder_1.newLine(); + _builder_1.append("}"); + _builder_1.newLine(); + this.assertCompilesTo(_builder, _builder_1); + } +} diff --git a/org.eclipse.xtext.xbase/src/org/eclipse/xtext/xbase/typesystem/conformance/TypeConformanceComputer.java b/org.eclipse.xtext.xbase/src/org/eclipse/xtext/xbase/typesystem/conformance/TypeConformanceComputer.java index ce470bcde5a..75f84931ea0 100644 --- a/org.eclipse.xtext.xbase/src/org/eclipse/xtext/xbase/typesystem/conformance/TypeConformanceComputer.java +++ b/org.eclipse.xtext.xbase/src/org/eclipse/xtext/xbase/typesystem/conformance/TypeConformanceComputer.java @@ -8,6 +8,7 @@ *******************************************************************************/ package org.eclipse.xtext.xbase.typesystem.conformance; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; @@ -252,6 +253,36 @@ protected LightweightTypeReference getCommonSuperType(List unconstrainedUnboundTypes = null; + for(LightweightTypeReference type: types) { + if (type.getKind() == LightweightTypeReference.KIND_UNBOUND_TYPE_REFERENCE) { + UnboundTypeReference casted = (UnboundTypeReference)type; + if (!casted.hasSignificantHints()) { + if (unconstrainedUnboundTypes == null) { + unconstrainedUnboundTypes = new ArrayList<>(types.size()); + } + unconstrainedUnboundTypes.add(casted); + } + } + } + if (unconstrainedUnboundTypes != null) { + List withoutUnconstrained = new ArrayList<>(types); + withoutUnconstrained.removeAll(unconstrainedUnboundTypes); + if (!withoutUnconstrained.isEmpty()) { + if (containsPrimitiveOrAnyReferences(withoutUnconstrained)) { + withoutUnconstrained = replacePrimitivesAndRemoveAnyReferences(types); + } + LightweightTypeReference intermediate = getCommonSuperType(withoutUnconstrained); + if (intermediate != null) { + unconstrainedUnboundTypes.add(0, intermediate); + types = unconstrainedUnboundTypes; + } + } + } + LightweightTypeReference conformsToAllOrVoid = findConformsToAllOrVoid(types); if (conformsToAllOrVoid != null) { if (conformsToAllOrVoid.isPrimitiveVoid()) { @@ -270,7 +301,7 @@ protected LightweightTypeReference getCommonSuperType(List types) { LightweightTypeReference firstType = types.get(0); - final List tail = types.subList(1, types.size()); + List tail = types.subList(1, types.size()); // mapping from rawtype to resolved parameterized types // used to determine the correct type arguments Multimap all = LinkedHashMultimap.create(); @@ -287,7 +318,8 @@ protected LightweightTypeReference doGetCommonSuperType(List referencesWithSameDistance = getMostSpecialCandidates(types, all, candidates); - return wrapInCompoundTypeIfNecessary(referencesWithSameDistance); + LightweightTypeReference result = wrapInCompoundTypeIfNecessary(referencesWithSameDistance); + return result; } protected LightweightTypeReference wrapInCompoundTypeIfNecessary(