Skip to content

Commit b08bfc5

Browse files
cirrasfourls
authored andcommitted
Handle variant conversions more accurately
This one was a real mess. The initial implementation of variant conversions for invocation resolution was based on the FPC `is_better_candidate_single_variant` routine because their documentation indicated it was reverse-engineered from Delphi's behavior. Unfortunately, it was inaccurate in many ways. Some interesting problems in the previous implementation: - Comparisons of integer types were based on type identity, while Delphi bases these comparisons on signedness and value range. - All real types (as well as `Currency` and `Comp`) were preferred over `Extended`, while Delphi always prefers it over other real types (plus `Currency` and `Comp`, but only when the`Extended` type is larger). - `Currency` and `Comp` didn't conflict with `Single` or `Extended`, while in Delphi they will always conflict with real types of the same size or smaller. - Character types (`WideChar`, `AnsiChar`) parameters were considered valid targets for variant conversions, while in Delphi they are not. - Mixing-and-matching candidates between string, integer, boolean, and real types was allowed, while in Delphi such cases are ambiguous.
1 parent 7648fee commit b08bfc5

File tree

7 files changed

+229
-321
lines changed

7 files changed

+229
-321
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Fixed
11+
12+
- Inaccurate handling of variant conversions, which resulted in overload resolution failures.
13+
1014
## [1.17.0] - 2025-05-15
1115

1216
### Added

delphi-frontend/src/main/java/au/com/integradev/delphi/symbol/resolve/InvocationCandidate.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public final class InvocationCandidate {
5757
private int structMismatchCount;
5858
private int proceduralDistance;
5959
private int codePageDistance;
60-
private final List<VariantConversionType> variantConversions;
60+
private final List<Type> variantConversions;
6161
private boolean invalid;
6262

6363
public InvocationCandidate(Invocable invocable) {
@@ -158,11 +158,11 @@ public int getCodePageDistance() {
158158
return codePageDistance;
159159
}
160160

161-
public void addVariantConversion(VariantConversionType variantConversionType) {
161+
public void addVariantConversion(Type variantConversionType) {
162162
variantConversions.add(variantConversionType);
163163
}
164164

165-
public VariantConversionType getVariantConversionType(int argumentIndex) {
165+
public Type getVariantConversionType(int argumentIndex) {
166166
return variantConversions.get(argumentIndex);
167167
}
168168

delphi-frontend/src/main/java/au/com/integradev/delphi/symbol/resolve/InvocationResolver.java

Lines changed: 97 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -25,24 +25,6 @@
2525
import static au.com.integradev.delphi.symbol.resolve.EqualityType.EQUAL;
2626
import static au.com.integradev.delphi.symbol.resolve.EqualityType.EXACT;
2727
import static au.com.integradev.delphi.symbol.resolve.EqualityType.INCOMPATIBLE_TYPES;
28-
import static au.com.integradev.delphi.symbol.resolve.VariantConversionType.ANSISTRING;
29-
import static au.com.integradev.delphi.symbol.resolve.VariantConversionType.BYTE;
30-
import static au.com.integradev.delphi.symbol.resolve.VariantConversionType.CARDINAL;
31-
import static au.com.integradev.delphi.symbol.resolve.VariantConversionType.DOUBLE_CURRENCY;
32-
import static au.com.integradev.delphi.symbol.resolve.VariantConversionType.DYNAMIC_ARRAY;
33-
import static au.com.integradev.delphi.symbol.resolve.VariantConversionType.ENUM;
34-
import static au.com.integradev.delphi.symbol.resolve.VariantConversionType.EXTENDED;
35-
import static au.com.integradev.delphi.symbol.resolve.VariantConversionType.FORMAL_BOOLEAN;
36-
import static au.com.integradev.delphi.symbol.resolve.VariantConversionType.INCOMPATIBLE_VARIANT;
37-
import static au.com.integradev.delphi.symbol.resolve.VariantConversionType.INTEGER;
38-
import static au.com.integradev.delphi.symbol.resolve.VariantConversionType.NO_CONVERSION_REQUIRED;
39-
import static au.com.integradev.delphi.symbol.resolve.VariantConversionType.SHORTINT;
40-
import static au.com.integradev.delphi.symbol.resolve.VariantConversionType.SHORTSTRING;
41-
import static au.com.integradev.delphi.symbol.resolve.VariantConversionType.SINGLE;
42-
import static au.com.integradev.delphi.symbol.resolve.VariantConversionType.SMALLINT;
43-
import static au.com.integradev.delphi.symbol.resolve.VariantConversionType.UNICODESTRING;
44-
import static au.com.integradev.delphi.symbol.resolve.VariantConversionType.WIDESTRING;
45-
import static au.com.integradev.delphi.symbol.resolve.VariantConversionType.WORD;
4628
import static java.lang.Math.abs;
4729
import static java.util.function.Predicate.not;
4830

@@ -52,7 +34,9 @@
5234
import com.google.common.collect.ComparisonChain;
5335
import java.util.ArrayList;
5436
import java.util.Collections;
37+
import java.util.Comparator;
5538
import java.util.List;
39+
import java.util.Objects;
5640
import java.util.Set;
5741
import java.util.stream.Collectors;
5842
import org.sonar.plugins.communitydelphi.api.type.CodePages;
@@ -192,7 +176,7 @@ private static void processArgument(
192176
}
193177

194178
checkCodePageDistance(candidate, argumentType, parameterType);
195-
checkVariantConversions(candidate, argumentType, parameterType);
179+
addVariantConversion(candidate, argumentType, parameterType);
196180

197181
// When an ambiguous procedural type was changed to an invocation, an exact match is
198182
// downgraded to equal.
@@ -352,22 +336,15 @@ private static void checkCodePageDistance(
352336
}
353337
}
354338

355-
// Keep track of implicit variant conversions
356-
// Also invalidate candidates that would produce invalid variant conversions
357-
private static void checkVariantConversions(
339+
private static void addVariantConversion(
358340
InvocationCandidate candidate, Type argumentType, Type parameterType) {
359-
argumentType = TypeUtils.findBaseType(argumentType);
360-
parameterType = TypeUtils.findBaseType(parameterType);
361-
VariantConversionType variantConversionType = NO_CONVERSION_REQUIRED;
362-
if (argumentType.isVariant()) {
363-
variantConversionType = VariantConversionType.fromType(parameterType);
364-
if (variantConversionType == INCOMPATIBLE_VARIANT) {
365-
candidate.setInvalid();
366-
}
367-
} else if (parameterType.isVariant()) {
368-
variantConversionType = VariantConversionType.fromType(argumentType);
341+
if (argumentType.isVariant() && !parameterType.isVariant()) {
342+
candidate.addVariantConversion(parameterType);
343+
} else if (parameterType.isVariant() && !argumentType.isVariant()) {
344+
candidate.addVariantConversion(argumentType);
345+
} else {
346+
candidate.addVariantConversion(null);
369347
}
370-
candidate.addVariantConversion(variantConversionType);
371348
}
372349

373350
private static EqualityType varParameterAllowed(Type argType, Parameter parameter) {
@@ -537,117 +514,102 @@ private int isBetterCandidate(InvocationCandidate candidate, InvocationCandidate
537514
private int getVariantDistance(InvocationCandidate candidate, InvocationCandidate bestCandidate) {
538515
int variantDistance = 0;
539516
for (int i = 0; i < arguments.size(); ++i) {
540-
VariantConversionType currentVcl = candidate.getVariantConversionType(i);
541-
VariantConversionType bestVcl = bestCandidate.getVariantConversionType(i);
542-
variantDistance += isBetterVariantConversion(currentVcl, bestVcl);
517+
Type current = candidate.getVariantConversionType(i);
518+
Type best = bestCandidate.getVariantConversionType(i);
519+
variantDistance += isBetterVariantConversion(current, best);
543520
}
544521
return variantDistance;
545522
}
546523

547-
/**
548-
* Determines which variant conversion type takes precedence when converting a variant type
549-
* argument to a parameter type.
550-
*
551-
* <p>Delphi precedence rules extracted from test programs:
552-
*
553-
* <ul>
554-
* <li>single > (char, currency, int64, shortstring, ansistring, widestring, unicodestring,
555-
* extended, double)
556-
* <li>double/currency > (char, int64, shortstring, ansistring, widestring, unicodestring,
557-
* extended)
558-
* <li>extended > (char, int64, shortstring, ansistring, widestring, unicodestring)
559-
* <li>longint/cardinal > (int64, shortstring, ansistring, widestring, unicodestring, extended,
560-
* double, single, char, currency)
561-
* <li>smallint > (longint, int64, shortstring, ansistring, widestring, unicodestring, extended,
562-
* double single, char, currency);
563-
* <li>word > (longint, cardinal, int64, shortstring, ansistring, widestring, unicodestring,
564-
* extended, double single, char, currency);
565-
* <li>shortint > (longint, smallint, int64, shortstring, ansistring, widestring, unicodestring,
566-
* extended, double, single, char, currency)
567-
* <li>byte > (longint, cardinal, word, smallint, int64, shortstring, ansistring, widestring,
568-
* unicodestring, extended, double, single, char, currency);
569-
* <li>boolean/formal > (char, int64, shortstring, ansistring, widestring, unicodestring)
570-
* <li>widestring > (char, int64, shortstring, ansistring, unicodestring)
571-
* <li>unicodestring > (char, int64, shortstring, ansistring)
572-
* <li>ansistring > (char, int64, shortstring)
573-
* <li>shortstring > (char, int64)
574-
* </ul>
575-
*
576-
* <p>Relations not mentioned mean that they conflict: no decision possible
577-
*
578-
* @param currentVcl The conversion type we're checking
579-
* @param bestVcl The best conversion type so far
580-
* @return
581-
* <ul>
582-
* <li>> 0 when currentVcl is better than bestVcl
583-
* <li>< 0 when bestVcl is better than currentVcl
584-
* <li>= 0 when both are equal
585-
* </ul>
586-
*
587-
* @see <a href="https://github.com/fpc/FPCSource/blob/main/compiler/htypechk.pas#L3367">
588-
* is_better_candidate_single_variant</a>
589-
*/
590-
private static int isBetterVariantConversion(
591-
VariantConversionType currentVcl, VariantConversionType bestVcl) {
592-
if (currentVcl == bestVcl) {
524+
private static int isBetterVariantConversion(Type current, Type best) {
525+
if (current == null && best == null) {
593526
return 0;
594-
} else if (currentVcl == INCOMPATIBLE_VARIANT || bestVcl == NO_CONVERSION_REQUIRED) {
595-
return -1;
596-
} else if (bestVcl == INCOMPATIBLE_VARIANT || currentVcl == NO_CONVERSION_REQUIRED) {
597-
return 1;
598-
} else if (currentVcl == FORMAL_BOOLEAN || bestVcl == FORMAL_BOOLEAN) {
599-
if (currentVcl == FORMAL_BOOLEAN) {
600-
return VariantConversionType.isChari64Str(bestVcl) ? 1 : 0;
601-
} else {
602-
return VariantConversionType.isChari64Str(currentVcl) ? -1 : 0;
603-
}
604-
} else if (currentVcl == BYTE || bestVcl == BYTE) {
605-
return calculateRelation(currentVcl, bestVcl, BYTE, Set.of(SHORTINT));
606-
} else if (currentVcl == SHORTINT || bestVcl == SHORTINT) {
607-
return calculateRelation(currentVcl, bestVcl, SHORTINT, Set.of(WORD, CARDINAL));
608-
} else if (currentVcl == WORD || bestVcl == WORD) {
609-
return calculateRelation(currentVcl, bestVcl, WORD, Set.of(SMALLINT));
610-
} else if (currentVcl == SMALLINT || bestVcl == SMALLINT) {
611-
return calculateRelation(currentVcl, bestVcl, SMALLINT, Set.of(CARDINAL));
612-
} else if (currentVcl == CARDINAL || bestVcl == CARDINAL) {
613-
return calculateRelation(currentVcl, bestVcl, CARDINAL, Set.of(INTEGER));
614-
} else if (currentVcl == INTEGER || bestVcl == INTEGER) {
615-
return (bestVcl == INTEGER) ? -1 : 1;
616-
} else if (currentVcl == SINGLE || bestVcl == SINGLE) {
617-
return (bestVcl == SINGLE) ? -1 : 1;
618-
} else if (currentVcl == DOUBLE_CURRENCY || bestVcl == DOUBLE_CURRENCY) {
619-
return (bestVcl == DOUBLE_CURRENCY) ? -1 : 1;
620-
} else if (currentVcl == EXTENDED || bestVcl == EXTENDED) {
621-
return (bestVcl == EXTENDED) ? -1 : 1;
622-
} else if (currentVcl == WIDESTRING || bestVcl == WIDESTRING) {
623-
return (bestVcl == WIDESTRING) ? -1 : 1;
624-
} else if (currentVcl == UNICODESTRING || bestVcl == UNICODESTRING) {
625-
return (bestVcl == UNICODESTRING) ? -1 : 1;
626-
} else if (currentVcl == ANSISTRING || bestVcl == ANSISTRING) {
627-
return (bestVcl == ANSISTRING) ? -1 : 1;
628-
} else if (currentVcl == SHORTSTRING || bestVcl == SHORTSTRING) {
629-
return (bestVcl == SHORTSTRING) ? -1 : 1;
630-
} else if (currentVcl == ENUM || bestVcl == ENUM) {
631-
return (bestVcl == ENUM) ? -1 : 1;
632-
} else if (currentVcl == DYNAMIC_ARRAY || bestVcl == DYNAMIC_ARRAY) {
633-
return (bestVcl == DYNAMIC_ARRAY) ? -1 : 1;
634-
}
635-
636-
// All possibilities should have been checked now.
637-
throw new AssertionError("Unhandled VariantConversionType!");
527+
}
528+
return ComparisonChain.start()
529+
.compare(current, best, Comparator.comparing(Objects::isNull))
530+
.compare(current, best, Comparator.comparing(InvocationResolver::isIInterface))
531+
.compare(current, best, Comparator.comparing(Type::isUntyped))
532+
.compare(current, best, InvocationResolver::compareNumericType)
533+
.compare(current, best, InvocationResolver::compareRealSize)
534+
.compare(current, best, InvocationResolver::compareIntegerRange)
535+
.compare(current, best, InvocationResolver::compareStringType)
536+
.result();
638537
}
639538

640-
private static int calculateRelation(
641-
VariantConversionType currentVcl,
642-
VariantConversionType bestVcl,
643-
VariantConversionType testVcl,
644-
Set<VariantConversionType> conflictTypes) {
645-
if (conflictTypes.contains(bestVcl) || conflictTypes.contains(currentVcl)) {
646-
return 0;
647-
} else if (bestVcl == testVcl) {
539+
private static boolean isIInterface(Type type) {
540+
return TypeUtils.findBaseType(type).is("System.IInterface");
541+
}
542+
543+
private static int compareNumericType(Type a, Type b) {
544+
if (a.isReal() && b.isInteger()) {
545+
return 1;
546+
} else if (b.isReal() && a.isInteger()) {
648547
return -1;
649548
} else {
650-
return 1;
549+
return 0;
550+
}
551+
}
552+
553+
private static int compareRealSize(Type a, Type b) {
554+
if (!a.isReal() || !b.isReal()) {
555+
return 0;
556+
}
557+
if (isCurrencyCompConflict(a, b) || isCurrencyCompConflict(b, a)) {
558+
return 0;
559+
}
560+
return Objects.compare(a, b, Comparator.comparingInt(Type::size));
561+
}
562+
563+
private static boolean isCurrencyCompConflict(Type currencyComp, Type real) {
564+
currencyComp = TypeUtils.findBaseType(currencyComp);
565+
return (currencyComp.is(IntrinsicType.CURRENCY) || currencyComp.is(IntrinsicType.COMP))
566+
&& real.isReal()
567+
&& currencyComp.size() >= real.size();
568+
}
569+
570+
private static int compareIntegerRange(Type a, Type b) {
571+
if (!a.isInteger() || !b.isInteger()) {
572+
return 0;
651573
}
574+
575+
IntegerType intA = (IntegerType) a;
576+
IntegerType intB = (IntegerType) b;
577+
578+
if (valueRangesAreAmbiguous(intA, intB)) {
579+
return 0;
580+
}
581+
582+
return ComparisonChain.start()
583+
.compare(intA, intB, Comparator.comparing(IntegerType::max))
584+
.compare(intB, intA, Comparator.comparing(IntegerType::min))
585+
.result();
586+
}
587+
588+
private static boolean valueRangesAreAmbiguous(IntegerType a, IntegerType b) {
589+
return a.isSigned() == b.isSigned()
590+
&& !(a.min().compareTo(b.min()) <= 0 && a.max().compareTo(b.max()) >= 0)
591+
&& !(b.min().compareTo(a.min()) <= 0 && b.max().compareTo(a.max()) >= 0);
592+
}
593+
594+
private static int compareStringType(Type a, Type b) {
595+
if (!a.isString() || !b.isString()) {
596+
return 0;
597+
}
598+
599+
return Comparator.<Type>comparingInt(
600+
type -> {
601+
type = TypeUtils.findBaseType(type);
602+
if (type.is(IntrinsicType.WIDESTRING)) {
603+
return 1;
604+
} else if (type.is(IntrinsicType.UNICODESTRING)) {
605+
return 2;
606+
} else if (type.isAnsiString()) {
607+
return 3;
608+
} else {
609+
return 4;
610+
}
611+
})
612+
.reversed()
613+
.compare(a, b);
652614
}
653615
}

0 commit comments

Comments
 (0)