Skip to content

Fix ambiguous type comparisons from variants to real types #392

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jul 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Fixed

- Ambiguous type comparisons from variants to real types.

## [1.17.1] - 2025-06-10

### Fixed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -530,7 +530,7 @@ private static int isBetterVariantConversion(Type current, Type best) {
.compare(current, best, Comparator.comparing(InvocationResolver::isIInterface))
.compare(current, best, Comparator.comparing(Type::isUntyped))
.compare(current, best, InvocationResolver::compareNumericType)
.compare(current, best, InvocationResolver::compareRealSize)
.compare(current, best, InvocationResolver::compareRealTypes)
.compare(current, best, InvocationResolver::compareIntegerRange)
.compare(current, best, InvocationResolver::compareStringType)
.result();
Expand All @@ -550,20 +550,35 @@ private static int compareNumericType(Type a, Type b) {
}
}

private static int compareRealSize(Type a, Type b) {
private static int compareRealTypes(Type a, Type b) {
if (!a.isReal() || !b.isReal()) {
return 0;
}

if (isCurrencyCompConflict(a, b) || isCurrencyCompConflict(b, a)) {
return 0;
}
return Objects.compare(a, b, Comparator.comparingInt(Type::size));

return Comparator.<Type>comparingInt(
type -> {
type = TypeUtils.findBaseType(type);
if (type.is(IntrinsicType.EXTENDED)) {
return 1;
} else if (type.is(IntrinsicType.DOUBLE)) {
return 2;
} else if (type.is(IntrinsicType.REAL48)) {
return 3;
} else {
return 4;
}
})
.reversed()
.compare(a, b);
}

private static boolean isCurrencyCompConflict(Type currencyComp, Type real) {
currencyComp = TypeUtils.findBaseType(currencyComp);
return (currencyComp.is(IntrinsicType.CURRENCY) || currencyComp.is(IntrinsicType.COMP))
&& real.isReal()
&& currencyComp.size() >= real.size();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@
import static org.sonar.plugins.communitydelphi.api.type.StructKind.RECORD;
import static org.sonar.plugins.communitydelphi.api.type.TypeFactory.untypedType;

import au.com.integradev.delphi.DelphiProperties;
import au.com.integradev.delphi.compiler.Toolchain;
import au.com.integradev.delphi.type.factory.ArrayOption;
import au.com.integradev.delphi.type.factory.TypeFactoryImpl;
import au.com.integradev.delphi.type.parameter.FormalParameter;
Expand All @@ -72,7 +74,10 @@
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import org.sonar.plugins.communitydelphi.api.ast.FormalParameterNode.FormalParameterData;
import org.sonar.plugins.communitydelphi.api.symbol.Invocable;
import org.sonar.plugins.communitydelphi.api.symbol.scope.DelphiScope;
Expand All @@ -83,23 +88,28 @@
import org.sonar.plugins.communitydelphi.api.type.TypeFactory;

class InvocationResolverTest {
private static final TypeFactory FACTORY = TypeFactoryUtils.defaultFactory();
private TypeFactory factory;
private Set<InvocationCandidate> resolved;

@BeforeEach
void setup() {
factory = TypeFactoryUtils.defaultFactory();
}

private void assertResolved(Type argumentType, Type winnerType, Type loserType) {
assertResolved(List.of(argumentType), List.of(winnerType), List.of(loserType));
}

private static Type type(IntrinsicType intrinsic) {
return FACTORY.getIntrinsic(intrinsic);
private Type type(IntrinsicType intrinsic) {
return factory.getIntrinsic(intrinsic);
}

private static Type subrange(String image, int min, int max) {
return FACTORY.subrange(image, BigInteger.valueOf(min), BigInteger.valueOf(max));
private Type subrange(String image, int min, int max) {
return factory.subrange(image, BigInteger.valueOf(min), BigInteger.valueOf(max));
}

private static Type subrange(String image, Type type) {
return FACTORY.subrange(image, type);
private Type subrange(String image, Type type) {
return factory.subrange(image, type);
}

private void assertResolved(
Expand Down Expand Up @@ -181,28 +191,36 @@ void testIntegerTypes() {
assertResolved(type(INTEGER), type(WORD), type(BYTE));
assertResolved(type(SHORTINT), type(NATIVEINT), type(NATIVEUINT));

Type hwnd = FACTORY.strongAlias("HWND", type(NATIVEUINT));
Type hwnd = factory.strongAlias("HWND", type(NATIVEUINT));
assertResolved(
List.of(hwnd, type(NATIVEUINT), type(SHORTINT), type(SHORTINT)),
List.of(hwnd, type(NATIVEUINT), type(NATIVEINT), type(NATIVEINT)),
List.of(hwnd, type(NATIVEUINT), type(NATIVEUINT), type(NATIVEINT)));

assertResolved(type(BYTE), type(INTEGER), type(DOUBLE));

assertResolved(FACTORY.strongAlias("MyWord", type(LONGWORD)), type(INT64), type(INTEGER));
assertResolved(type(LONGWORD), FACTORY.strongAlias("MyInt64", type(INT64)), type(INTEGER));
assertResolved(factory.strongAlias("MyWord", type(LONGWORD)), type(INT64), type(INTEGER));
assertResolved(type(LONGWORD), factory.strongAlias("MyInt64", type(INT64)), type(INTEGER));
}

@Test
void testFloatingPointTypes() {
@ParameterizedTest
@EnumSource(
value = Toolchain.class,
names = {"DCC32", "DCC64"})
void testRealTypes(Toolchain toolchain) {
factory = new TypeFactoryImpl(toolchain, DelphiProperties.COMPILER_VERSION_DEFAULT);
assertResolved(type(EXTENDED), type(DOUBLE), type(SINGLE));
assertResolved(type(EXTENDED), type(REAL), type(SINGLE));
assertResolved(type(DOUBLE), type(EXTENDED), type(SINGLE));
assertResolved(type(REAL), type(EXTENDED), type(SINGLE));
}

@Test
void testMixedToFloatingPointTypes() {
@ParameterizedTest
@EnumSource(
value = Toolchain.class,
names = {"DCC32", "DCC64"})
void testMixedToRealTypes(Toolchain toolchain) {
factory = new TypeFactoryImpl(toolchain, DelphiProperties.COMPILER_VERSION_DEFAULT);
assertResolved(
List.of(type(EXTENDED), type(INTEGER)),
List.of(type(EXTENDED), type(EXTENDED)),
Expand All @@ -217,8 +235,12 @@ void testMixedToFloatingPointTypes() {
List.of(type(EXTENDED), type(EXTENDED)));
}

@Test
void testIntegerToFloatingPointTypes() {
@ParameterizedTest
@EnumSource(
value = Toolchain.class,
names = {"DCC32", "DCC64"})
void testIntegerToRealTypes(Toolchain toolchain) {
factory = new TypeFactoryImpl(toolchain, DelphiProperties.COMPILER_VERSION_DEFAULT);
assertResolved(
List.of(type(SHORTINT), type(SHORTINT)),
List.of(type(INTEGER), type(INTEGER)),
Expand All @@ -236,12 +258,12 @@ void testIntegerToFloatingPointTypes() {
@Test
void testTextTypes() {
assertResolved(
FACTORY.strongAlias("MyString", type(UNICODESTRING)),
factory.strongAlias("MyString", type(UNICODESTRING)),
type(UNICODESTRING),
type(SHORTSTRING));
assertResolved(
type(UNICODESTRING),
FACTORY.strongAlias("MyString", type(UNICODESTRING)),
factory.strongAlias("MyString", type(UNICODESTRING)),
type(SHORTSTRING));
assertResolved(
List.of(type(ANSICHAR), type(ANSICHAR)),
Expand Down Expand Up @@ -291,8 +313,8 @@ void testTextTypes() {
assertResolved(type(ANSISTRING), type(VARIANT), type(SHORTSTRING));
assertResolved(type(SHORTSTRING), type(ANSISTRING), type(VARIANT));

assertResolved(type(PANSICHAR), type(ANSISTRING), FACTORY.ansiString(CodePages.CP_1252));
assertResolved(type(PANSICHAR), FACTORY.ansiString(CodePages.CP_1252), type(UNICODESTRING));
assertResolved(type(PANSICHAR), type(ANSISTRING), factory.ansiString(CodePages.CP_1252));
assertResolved(type(PANSICHAR), factory.ansiString(CodePages.CP_1252), type(UNICODESTRING));
assertResolved(type(PANSICHAR), type(UNICODESTRING), type(WIDESTRING));
assertResolved(type(PANSICHAR), type(WIDESTRING), type(SHORTSTRING));

Expand All @@ -302,12 +324,17 @@ void testTextTypes() {
assertIncompatible(type(PWIDECHAR), type(SHORTSTRING));
}

@Test
void testVariantTypes() {
@ParameterizedTest
@EnumSource(
value = Toolchain.class,
names = {"DCC32", "DCC64"})
void testVariantTypes(Toolchain toolchain) {
factory = new TypeFactoryImpl(toolchain, DelphiProperties.COMPILER_VERSION_DEFAULT);

Type incompatibleType = TypeMocker.struct("Incompatible", RECORD);
Type enumeration = ((TypeFactoryImpl) FACTORY).enumeration("E", DelphiScope.unknownScope());
Type enumeration = ((TypeFactoryImpl) factory).enumeration("E", DelphiScope.unknownScope());
Type dynamicArray =
((TypeFactoryImpl) FACTORY).array(null, type(INTEGER), Set.of(ArrayOption.DYNAMIC));
((TypeFactoryImpl) factory).array(null, type(INTEGER), Set.of(ArrayOption.DYNAMIC));

assertResolved(
List.of(type(UNICODESTRING), incompatibleType, type(BOOLEAN)),
Expand All @@ -318,9 +345,9 @@ void testVariantTypes() {
assertResolved(type(SHORTSTRING), type(ANSISTRING), type(VARIANT));
assertResolved(type(ANSISTRING), type(VARIANT), type(SHORTSTRING));
assertResolved(type(ANSISTRING), type(UNICODESTRING), type(VARIANT));
assertResolved(type(ANSISTRING), FACTORY.ansiString(CodePages.CP_UTF8), type(VARIANT));
assertResolved(type(ANSISTRING), FACTORY.ansiString(CodePages.CP_1252), type(VARIANT));
assertResolved(FACTORY.ansiString(1251), FACTORY.ansiString(1252), type(VARIANT));
assertResolved(type(ANSISTRING), factory.ansiString(CodePages.CP_UTF8), type(VARIANT));
assertResolved(type(ANSISTRING), factory.ansiString(CodePages.CP_1252), type(VARIANT));
assertResolved(factory.ansiString(1251), factory.ansiString(1252), type(VARIANT));

assertResolved(
List.of(type(VARIANT), type(BYTE)),
Expand All @@ -338,9 +365,7 @@ void testVariantTypes() {
assertResolved(type(VARIANT), TypeFactory.untypedType(), type(UNICODESTRING));
assertResolved(type(VARIANT), TypeFactory.untypedType(), type(BOOLEAN));

assertResolved(type(VARIANT), type(EXTENDED), type(CURRENCY));
assertResolved(type(VARIANT), type(CURRENCY), type(UINT64));
assertResolved(type(VARIANT), type(EXTENDED), type(COMP));
assertResolved(type(VARIANT), type(COMP), type(UINT64));

assertAmbiguous(type(VARIANT), type(CURRENCY), type(DOUBLE));
Expand Down Expand Up @@ -391,6 +416,19 @@ void testVariantTypes() {
assertIncompatible(type(VARIANT), type(ANSICHAR));
}

@Test
void testVariantToExtendedCurrencyShouldResolveOnDcc32() {
assertResolved(type(VARIANT), type(EXTENDED), type(CURRENCY));
assertResolved(type(VARIANT), type(EXTENDED), type(COMP));
}

@Test
void testVariantToExtendedCurrencyShouldBeAmbiguousOnDcc64() {
factory = new TypeFactoryImpl(Toolchain.DCC64, DelphiProperties.COMPILER_VERSION_DEFAULT);
assertAmbiguous(type(VARIANT), type(EXTENDED), type(CURRENCY));
assertAmbiguous(type(VARIANT), type(EXTENDED), type(COMP));
}

@Test
void testInheritedTypes() {
Type grandparent = TypeMocker.struct("Grandparent", CLASS);
Expand All @@ -404,16 +442,16 @@ void testInheritedTypes() {
@Test
void testVarParameters() {
Type openArray =
((TypeFactoryImpl) FACTORY).array(null, type(INTEGER), Set.of(ArrayOption.OPEN));
((TypeFactoryImpl) factory).array(null, type(INTEGER), Set.of(ArrayOption.OPEN));
Type dynamicArray =
((TypeFactoryImpl) FACTORY).array(null, type(INTEGER), Set.of(ArrayOption.DYNAMIC));
((TypeFactoryImpl) factory).array(null, type(INTEGER), Set.of(ArrayOption.DYNAMIC));

assertResolvedVar(type(INTEGER), untypedType());
assertResolvedVar(dynamicArray, openArray);
assertResolvedVar(type(INTEGER), openArray);
assertResolvedVar(FACTORY.untypedPointer(), FACTORY.untypedPointer());
assertResolvedVar(factory.untypedPointer(), factory.untypedPointer());
assertResolvedVar(
FACTORY.pointerTo(null, type(INTEGER)), FACTORY.pointerTo(null, type(SHORTINT)));
assertResolvedVar(FACTORY.fileOf(type(INTEGER)), FACTORY.untypedFile());
factory.pointerTo(null, type(INTEGER)), factory.pointerTo(null, type(SHORTINT)));
assertResolvedVar(factory.fileOf(type(INTEGER)), factory.untypedFile());
}
}