Skip to content

Commit 556bb60

Browse files
committed
Allow direct cast (abstracts) of typeParameters if underlying type matches target type.
+ check assign references for hints when determining array literal type.
1 parent 0ec8597 commit 556bb60

File tree

9 files changed

+101
-36
lines changed

9 files changed

+101
-36
lines changed

src/main/java/com/intellij/plugins/haxe/model/evaluator/HaxeExpressionEvaluatorHandlers.java

+15
Original file line numberDiff line numberDiff line change
@@ -2295,6 +2295,21 @@ static ResultHolder findExpectedTypeForUnify(@NotNull PsiElement element) {
22952295
}
22962296
}
22972297
}
2298+
if(parent instanceof HaxeAssignExpression assignExpression) {
2299+
HaxeExpression leftExpression = assignExpression.getLeftExpression();
2300+
if(leftExpression instanceof HaxeReferenceExpression referenceExpression) {
2301+
PsiElement resolve = referenceExpression.resolve();
2302+
if(resolve instanceof HaxePsiField field) {
2303+
HaxeTypeTag tag = field.getTypeTag();
2304+
if (tag != null) {
2305+
ResultHolder typeTag = HaxeTypeResolver.getTypeFromTypeTag(tag, element);
2306+
if (!typeTag.isUnknown()) {
2307+
return typeTag;
2308+
}
2309+
}
2310+
}
2311+
}
2312+
}
22982313
if (parent instanceof HaxeCallExpressionList callExpressionList
22992314
&& callExpressionList.getParent() instanceof HaxeCallExpression callExpression) {
23002315

src/main/java/com/intellij/plugins/haxe/model/evaluator/assign/AssignEvaluationSettings.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ public record AssignEvaluationSettings(
55
boolean checkDirectCasts,
66
boolean checkImplicitCasts,
77
boolean contravariance,
8-
boolean ignoreFromConstraints
8+
boolean ignoreFromConstraints,
9+
boolean implicitTypeMustMatchUnderlying
910
) {
1011
}

src/main/java/com/intellij/plugins/haxe/model/evaluator/assign/HaxeAssignEvaluation.java

+26-7
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ public HaxeAssignEvaluation(@NotNull ResultHolder to, @NotNull ResultHolder from
5757
this.from = from.getType();
5858
}
5959
// contexts used to check if we are in a scope with macro keyword
60-
toContext = to.getElementContext();
61-
fromContext = from.getElementContext();
60+
toContext = this.to.getElementContext();
61+
fromContext = this.from.getElementContext();
6262
}
6363

6464
public void fullyResolveTypes() {
@@ -302,7 +302,7 @@ static private boolean canAssignToFromFunction(
302302
toArgtype = tryExtractRestType(toArgtype);
303303
}
304304
}
305-
boolean argCompatibility = HaxeTypeCompatible.canAssignToFromContravariance(toArgtype, fromArgType, true, false);
305+
boolean argCompatibility = HaxeTypeCompatible.canAssignToFromContravariance(toArgtype, fromArgType, true, false, false);
306306
if (!argCompatibility) {
307307
return false;
308308
}
@@ -400,7 +400,7 @@ public void testTypeParameterConstraints(boolean checkDirectCasts, boolean check
400400
* Note: abstracts can be of all kinds of types(class, function enum, anonymous structures etc.) and can also be cased to these types
401401
* So there's a lot to check for here.
402402
*/
403-
public void testAbstractAssignRules(boolean checkDirectCasts, boolean checkImplicitCasts) {
403+
public void testAbstractAssignRules(boolean checkDirectCasts, boolean checkImplicitCasts, boolean implicitTypeMustMatchUnderlying) {
404404
if (to instanceof SpecificHaxeClassReference toClassReference && from instanceof SpecificHaxeClassReference fromClassReference ) {
405405

406406
HaxeClassModel toModel = toClassReference.getHaxeClassModel();
@@ -582,10 +582,15 @@ else if (from instanceof SpecificHaxeClassReference fromClassReference) {
582582
*/
583583
private @Nullable Boolean canAssignUsingDirectCastFrom(SpecificHaxeClassReference toClassReference, SpecificTypeReference fromClassReference) {
584584
return directCastRecursionGuard.computePreventingRecursion(toClassReference.context, true, () -> {
585+
585586
List<SpecificTypeReference> directCasts = toClassReference.getDirectCastFromTypes();
586587
for (SpecificTypeReference directCastType : directCasts) {
587588
// direct casts can be "chained" (ex. Int -> Float -> Single)
588589
if (HaxeTypeCompatible.canAssignToFromReference(directCastType, fromClassReference, true, false)) {
590+
//
591+
if(config.implicitTypeMustMatchUnderlying()) {
592+
if (underlyingTypeAndCastCheck(toClassReference, directCastType)) continue;
593+
}
589594
complete(true, "Abstract (from) direct cast match");
590595
return true;
591596
}
@@ -594,16 +599,30 @@ else if (from instanceof SpecificHaxeClassReference fromClassReference) {
594599
});
595600
}
596601

602+
// TODO mlo: need verification
603+
// its unclear to me how / when type parameters of abstracts can be directly casted. from some simple tests it seems to be a
604+
// requirement that the abstracts direct cast matches its underlying type (only when working with typeParameters).
605+
private static boolean underlyingTypeAndCastCheck(SpecificHaxeClassReference toClassReference, SpecificTypeReference directCastType) {
606+
HaxeClassModel haxeClassModel = toClassReference.getHaxeClassModel();
607+
if (haxeClassModel != null) {
608+
SpecificTypeReference underlyingType = haxeClassModel.getUnderlyingType();
609+
if (underlyingType != null && !underlyingType.isSameType(directCastType)) {
610+
return true;
611+
}
612+
}
613+
return false;
614+
}
615+
597616

598617
static boolean canAssignTypeParameters(HaxeAssignEvaluation context, @NotNull ResultHolder[] toSpecifics, @NotNull ResultHolder[] fromSpecifics) {
599-
return canAssignTypeParameters(context,toSpecifics,fromSpecifics, false);
618+
return canAssignTypeParameters(context,toSpecifics,fromSpecifics, false, false);
600619
}
601-
static boolean canAssignTypeParameters(HaxeAssignEvaluation context, @NotNull ResultHolder[] toSpecifics, @NotNull ResultHolder[] fromSpecifics, boolean ignoreFromConstraints) {
620+
static boolean canAssignTypeParameters(HaxeAssignEvaluation context, @NotNull ResultHolder[] toSpecifics, @NotNull ResultHolder[] fromSpecifics, boolean ignoreFromConstraints, boolean implicitTypeMustMatchUnderlying) {
602621
if (toSpecifics.length != fromSpecifics.length) return false;
603622
for (int i = 0, length = toSpecifics.length; i < length; i++) {
604623
ResultHolder toSpecific = toSpecifics[i];
605624
ResultHolder fromSpecific = fromSpecifics[i];
606-
if (!HaxeTypeCompatible.canAssignToFromTypeParameter(context, toSpecific, fromSpecific, ignoreFromConstraints)) {
625+
if (!HaxeTypeCompatible.canAssignToFromTypeParameter(context, toSpecific, fromSpecific, ignoreFromConstraints, implicitTypeMustMatchUnderlying)) {
607626
return false;
608627
}
609628
}

src/main/java/com/intellij/plugins/haxe/model/evaluator/assign/HaxeClassAssignUtil.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public class HaxeClassAssignUtil {
1919

2020
static boolean sameTypeCheck(HaxeAssignEvaluation context, SpecificHaxeClassReference toClassReference, SpecificHaxeClassReference fromClassReference) {
2121
if (toClassReference.getHaxeClass() == fromClassReference.getHaxeClass()) {
22-
if (canAssignTypeParameters(context, toClassReference.getSpecifics(), fromClassReference.getSpecifics(), context.getConfig().ignoreFromConstraints())) {
22+
if (canAssignTypeParameters(context, toClassReference.getSpecifics(), fromClassReference.getSpecifics(), context.getConfig().ignoreFromConstraints(), true)) {
2323
return true;
2424
}
2525
}

src/main/java/com/intellij/plugins/haxe/model/evaluator/assign/HaxeTypeCompatible.java

+15-15
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ public class HaxeTypeCompatible {
1414

1515
//TODO mlo: add some kind of recursion guard (implicit cast can loop around)?
1616

17-
private static final AssignEvaluationSettings DEFAULT_SETTINGS = new AssignEvaluationSettings(false, true,true, false, false);
18-
private static final AssignEvaluationSettings DEFAULT_STRICT_SETTINGS = new AssignEvaluationSettings(true, false, false, false, false);
19-
private static final AssignEvaluationSettings CONTRAVARIANCE_SETTINGS = new AssignEvaluationSettings(false, true, true, true, false);
20-
private static final AssignEvaluationSettings CALL_EXPRESSION_SETTINGS = new AssignEvaluationSettings(false, true, true, false, true);
17+
private static final AssignEvaluationSettings DEFAULT_SETTINGS = new AssignEvaluationSettings(false, true,true, false, false, false);
18+
private static final AssignEvaluationSettings DEFAULT_STRICT_SETTINGS = new AssignEvaluationSettings(true, false, false, false, false, false);
19+
private static final AssignEvaluationSettings CONTRAVARIANCE_SETTINGS = new AssignEvaluationSettings(false, true, true, true, false, false);
20+
private static final AssignEvaluationSettings CALL_EXPRESSION_SETTINGS = new AssignEvaluationSettings(false, true, true, false, true, false);
2121

2222
/**
2323
* Regular can assign will allow Dynamic to be assigned to anything and also check implicit casts (@:to/@From) and handle special annotation rules
@@ -34,7 +34,7 @@ static public boolean canAssignToFromReference(HaxeAssignEvaluation context, @Nu
3434

3535
static public boolean canAssignToFromReference(@Nullable SpecificTypeReference to, @Nullable SpecificTypeReference from, boolean checkDirectCasts, boolean checkImplicitCasts) {
3636
if (to == null || from == null) return false;
37-
AssignEvaluationSettings settings = new AssignEvaluationSettings(false, checkDirectCasts, checkImplicitCasts, false, false);
37+
AssignEvaluationSettings settings = new AssignEvaluationSettings(false, checkDirectCasts, checkImplicitCasts, false, false, false);
3838
return canAssignToFromEvaluation(to.createHolder(), from.createHolder(), settings, null).result;
3939
}
4040

@@ -54,9 +54,9 @@ static public boolean canAssignToFromContravariance(@Nullable ResultHolder to, @
5454
return canAssignToFromEvaluation(to, from, CONTRAVARIANCE_SETTINGS, null).result;
5555
}
5656

57-
static public boolean canAssignToFromContravariance(@Nullable ResultHolder to, @Nullable ResultHolder from, boolean checkDirectCasts, boolean checkImplicitCasts) {
57+
static public boolean canAssignToFromContravariance(@Nullable ResultHolder to, @Nullable ResultHolder from, boolean checkDirectCasts, boolean checkImplicitCasts, boolean implicitTypeMustMatchUnderlying) {
5858
if (to == null || from == null) return false;
59-
AssignEvaluationSettings settings = new AssignEvaluationSettings(false, checkDirectCasts, checkImplicitCasts, true, false);
59+
AssignEvaluationSettings settings = new AssignEvaluationSettings(false, checkDirectCasts, checkImplicitCasts, true, false, implicitTypeMustMatchUnderlying);
6060
return canAssignToFromEvaluation(to, from, settings, null).result;
6161
}
6262

@@ -70,7 +70,7 @@ static public HaxeAssignEvaluation evaluateAssignToFromForNewAndCallExpression(@
7070

7171
static public boolean canAssignToFromReference(@Nullable ResultHolder to, @Nullable ResultHolder from, boolean checkDirectCasts, boolean checkImplicitCasts, boolean contravariance) {
7272
if (to == null || from == null) return false;
73-
AssignEvaluationSettings settings = new AssignEvaluationSettings(false, checkDirectCasts, checkImplicitCasts, contravariance, false);
73+
AssignEvaluationSettings settings = new AssignEvaluationSettings(false, checkDirectCasts, checkImplicitCasts, contravariance, false, false);
7474
return canAssignToFromEvaluation(to, from, false, checkDirectCasts, checkImplicitCasts, contravariance).result;
7575
}
7676

@@ -104,12 +104,12 @@ static public HaxeAssignEvaluation evaluateAssignToFrom(@NotNull ResultHolder to
104104
*/
105105
static public boolean canAssignToFromTypeParameter(@Nullable ResultHolder to, @Nullable ResultHolder from) {
106106
if (to == null || from == null) return false;
107-
return canAssignToFromTypeParameter(null, to, from, false);
107+
return canAssignToFromTypeParameter(null, to, from, false, false);
108108
}
109109

110-
static public boolean canAssignToFromTypeParameter(HaxeAssignEvaluation context, @Nullable ResultHolder to, @Nullable ResultHolder from, boolean ignoreFromConstraints) {
110+
static public boolean canAssignToFromTypeParameter(HaxeAssignEvaluation context, @Nullable ResultHolder to, @Nullable ResultHolder from, boolean ignoreFromConstraints, boolean implicitTypeMustMatchUnderlying) {
111111
if (to == null || from == null) return false;
112-
return canAssignToFromStrictEvaluation(context, to, from, ignoreFromConstraints).result;
112+
return canAssignToFromStrictEvaluation(context, to, from, ignoreFromConstraints, implicitTypeMustMatchUnderlying).result;
113113
}
114114

115115
/**
@@ -121,9 +121,9 @@ private static HaxeAssignEvaluation canAssignToFromStrictEvaluation(HaxeAssignEv
121121
return canAssignToFromEvaluation(to, from, DEFAULT_STRICT_SETTINGS, context);
122122
}
123123

124-
private static HaxeAssignEvaluation canAssignToFromStrictEvaluation(HaxeAssignEvaluation context, @NotNull ResultHolder to, @NotNull ResultHolder from, boolean ignoreFromConstraints) {
124+
private static HaxeAssignEvaluation canAssignToFromStrictEvaluation(HaxeAssignEvaluation context, @NotNull ResultHolder to, @NotNull ResultHolder from, boolean ignoreFromConstraints, boolean implicitTypeMustMatchUnderlying) {
125125
// Note: There's a hack in abstract canAssign that allow assign when abstracts underlying type is Dynamic and it got direct "from Dynamic" cast
126-
AssignEvaluationSettings settings = new AssignEvaluationSettings(true, false, false, false, ignoreFromConstraints);
126+
AssignEvaluationSettings settings = new AssignEvaluationSettings(true, true, false, false, ignoreFromConstraints, implicitTypeMustMatchUnderlying);
127127
return canAssignToFromEvaluation(to, from, settings, context);
128128
}
129129

@@ -163,7 +163,7 @@ static public HaxeAssignEvaluation canAssignToFromEvaluation(@NotNull ResultHold
163163
boolean contravariance,
164164
@Nullable HaxeAssignEvaluation parent
165165
) {
166-
AssignEvaluationSettings settings = new AssignEvaluationSettings(strictBasicCheck, checkDirectCasts, checkImplicitCasts, contravariance, false);
166+
AssignEvaluationSettings settings = new AssignEvaluationSettings(strictBasicCheck, checkDirectCasts, checkImplicitCasts, contravariance, false, false);
167167
return canAssignToFromEvaluation(to,from, settings, parent);
168168
}
169169
static public HaxeAssignEvaluation canAssignToFromEvaluation(@NotNull ResultHolder to, @NotNull ResultHolder from,
@@ -189,7 +189,7 @@ static public HaxeAssignEvaluation canAssignToFromEvaluation(@NotNull ResultHold
189189
if (!evaluation.completed) evaluation.testEnumAssignRules();
190190
if (!evaluation.completed) evaluation.testFunctionAssignRules();
191191
if (!evaluation.completed) evaluation.testAnonymousAssignRules();
192-
if (!evaluation.completed) evaluation.testAbstractAssignRules(settings.checkDirectCasts(), settings.checkImplicitCasts());
192+
if (!evaluation.completed) evaluation.testAbstractAssignRules(settings.checkDirectCasts(), settings.checkImplicitCasts(), settings.implicitTypeMustMatchUnderlying());
193193
if (!evaluation.completed) evaluation.testTypeParameterConstraints(settings.checkDirectCasts(), settings.checkImplicitCasts());
194194
return true;
195195
});

src/test/java/com/intellij/plugins/haxe/ide/HaxeSemanticAnnotatorTest.java

+4
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,10 @@ public void testInitializeIntWithClass() throws Exception {
611611
doTestNoFixWithWarnings();
612612
}
613613

614+
@Test
615+
public void testArrayLiteralTypeDetectionAndCast() throws Exception {
616+
doTestNoFixWithWarnings();
617+
}
614618
@Test
615619
public void testAssignClassToInt() throws Exception {
616620
doTestNoFixWithWarnings();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package ;
2+
3+
class AssignArrayLiteral {
4+
var fieldColor:Array<TestColor>;
5+
var propertyColor(default, default):Array<TestColor>;
6+
7+
var fieldInt:Array<Int>;
8+
var propertyInt(default, default):Array<Int>;
9+
10+
public function new() {
11+
var variableColor:Array<TestColor>;
12+
var variableInt:Array<Int>;
13+
// correct
14+
variableColor = [0xFF008000, 0xFFFF0000];
15+
fieldColor = [0xFF008000, 0xFFFF0000];
16+
propertyColor = [0xFF008000, 0xFFFF0000];
17+
18+
fieldInt = fieldColor;
19+
propertyInt = propertyColor;
20+
variableInt = variableColor;
21+
22+
argumentTest(variableColor, fieldColor, propertyColor);
23+
argumentTest(variableInt, fieldInt, propertyColor);
24+
25+
//wrong
26+
variableColor = <error descr="Incompatible type: Array<String> should be Array<TestColor>">["0xFF008000", "0xFFFF0000"]</error>;
27+
fieldColor = <error descr="Incompatible type: Array<String> should be Array<TestColor>">["0xFF008000", "0xFFFF0000"]</error>;
28+
propertyColor = <error descr="Incompatible type: Array<String> should be Array<TestColor>">["0xFF008000", "0xFFFF0000"]</error>;
29+
30+
}
31+
32+
public function argumentTest(x:Array<TestColor>, y:Array<Int>, z:Array<UInt>) {}
33+
}
34+
35+
36+
abstract TestColor(Int) from Int from UInt to Int to UInt {}

src/test/resources/testData/annotation.semantic/FunctionBind1.hx

+1-6
Original file line numberDiff line numberDiff line change
@@ -36,16 +36,11 @@ class FunctionBindTest1 {
3636
var bind:(...Float) -> String = varargs.bind(1, _);
3737
var bind:haxe.Rest<Float> -> String = varargs.bind(1, _);
3838
var bind:Int -> String = varargs.bind(_, [1.0, 2.0, 3.0]);
39-
39+
var bind = varargs.bind(_, [1, 2, 3]);
4040

4141
//wrong
4242
var bind = varargs.bind(1, <error descr="Type mismatch (Expected: 'haxe.Rest<Float>' got: 'String')">"string"</error>); // String should be haxe.Rest<Float>
4343
var bind = varargs.bind(<error descr="Too many arguments (expected 2 but got 3)\"">1, _, _</error> ); // Too many callback arguments
4444

45-
46-
//TODOS
47-
48-
//TODO should be allowed (literal array should be allowed to be interpetated as array floats)
49-
var bind = varargs.bind(_, <error descr="Type mismatch (Expected: 'haxe.Rest<Float>' got: 'Array<Int>')">[1, 2, 3]</error>);
5045
}
5146
}

src/test/resources/testData/annotation.semantic/FunctionBind2.hx

+1-6
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,12 @@ class FunctionBindTest2 {
3232
var bind:(...Float) -> String = varargs.bind(1, _);
3333
var bind:haxe.Rest<Float > -> String = varargs.bind( 1 , _);
3434
var bind:Int -> String = varargs.bind(_, [1.0, 2.0, 3.0]);
35-
35+
var bind = varargs.bind(_, [1, 2, 3]);
3636

3737
//wrong
3838
var bind = varargs.bind(1, <error descr="Type mismatch (Expected: 'haxe.Rest<Float>' got: 'String')">"string"</error>); // String should be haxe.Rest<Float>
3939
var bind = varargs.bind(<error descr="Too many arguments (expected 2 but got 3)\"">1, _, _</error> ); // Too many callback arguments
4040

41-
42-
//TODOS
43-
44-
//TODO should be allowed (literal array should be allowed to be interpetated as array floats)
45-
var bind = varargs.bind(_, <error descr="Type mismatch (Expected: 'haxe.Rest<Float>' got: 'Array<Int>')">[1, 2, 3]</error>);
4641
}
4742

4843
function normal(x:Int, y:Float):String {return null;}

0 commit comments

Comments
 (0)