Skip to content

Commit

Permalink
init support for @:structInit
Browse files Browse the repository at this point in the history
  • Loading branch information
m0rkeulv committed Aug 4, 2024
1 parent ea4115a commit a74a8d6
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public static void check(final HaxeFieldDeclaration var, final AnnotationHolder
.range(var)
.create();
}
else if (!isFieldInitializedInTheConstructor(field)) {
else if (!isFieldInitializedInTheConstructor(field) && !field.getDeclaringClass().isStructInit()) {
holder.newAnnotation(HighlightSeverity.ERROR, HaxeBundle.message("haxe.semantic.final.var.init", field.getName()))
.range(var)
.create();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import com.intellij.plugins.haxe.lang.psi.*;
import com.intellij.plugins.haxe.lang.psi.impl.AbstractHaxePsiClass;
import com.intellij.plugins.haxe.lang.psi.impl.HaxeObjectLiteralImpl;
import com.intellij.plugins.haxe.metadata.HaxeMetadataList;
import com.intellij.plugins.haxe.metadata.psi.HaxeMeta;
import com.intellij.plugins.haxe.metadata.psi.HaxeMetadataCompileTimeMeta;
Expand Down Expand Up @@ -133,6 +134,9 @@ public boolean isAbstractType() {
public boolean isAnonymous() {
return haxeClass instanceof HaxeAnonymousType;
}
public boolean isObjectLiteral() {
return haxeClass instanceof HaxeObjectLiteralImpl;
}

public boolean isCoreType() {
return hasCompileTimeMeta(HaxeMeta.CORE_TYPE);
Expand Down Expand Up @@ -1041,4 +1045,8 @@ private boolean canAssignToFirstParam(HaxeMethodModel method, ResultHolder class
}
return false;
}

public boolean isStructInit() {
return hasCompileTimeMeta(HaxeMeta.STRUCT_INIT);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,8 @@ static private boolean canAssignToFromType(
}
}
}
if (to.getHaxeClassModel() != null && to.getHaxeClassModel().isAnonymous()) {
if (to.getHaxeClassModel() != null
&& ( to.getHaxeClassModel().isAnonymous() || to.getHaxeClassModel().isStructInit()) ) {
// compare members (Anonymous stucts can be "used" as interface)
if (containsAllMembers(to, from)) return true;
}
Expand Down Expand Up @@ -532,28 +533,44 @@ private static boolean containsAllMembers(SpecificHaxeClassReference to, Specifi
if (toClassModel == null || fromClassModel == null)
return false;

boolean isStruct = toClassModel.isStructInit();
List<HaxeBaseMemberModel> toMembers = toClassModel.getAllMembers(to.getGenericResolver());
List<HaxeBaseMemberModel> fromMembers = fromClassModel.getAllMembers(from.getGenericResolver());

for (HaxeBaseMemberModel member : toMembers) {
String name = member.getName();
// TODO type check parameter and return type
boolean memberExists;
boolean optional;
boolean memberExists = false;
boolean optional = false;
boolean ignored = false;
if (member instanceof HaxeMethodModel methodModel){
optional = false;
memberExists = fromMembers.stream().filter(model -> model instanceof HaxeMethodModel)
.map(model -> (HaxeMethodModel) model)
.filter(mm -> methodModel.getParameters().size() == mm.getParameters().size())
.anyMatch(model -> model.getNamePsi().textMatches(name));
if(!isStruct) {
memberExists = fromMembers.stream().filter(model -> model instanceof HaxeMethodModel)
.map(model -> (HaxeMethodModel)model)
.filter(mm -> methodModel.getParameters().size() == mm.getParameters().size())
.anyMatch(model -> model.getNamePsi().textMatches(name));
}else {
// ignore methods in @:structInit classes
ignored = true;
}
}else if (member instanceof HaxeFieldModel fieldModel) {
optional = fieldModel.isOptional();
ignored = fieldModel.isStatic();
memberExists = fromMembers.stream().anyMatch(model -> model.getNamePsi().textMatches(name));
}else {
optional = false;
memberExists = fromMembers.stream().anyMatch(model -> model.getNamePsi().textMatches(name));
}

if (!memberExists && !optional) return false;
if (!ignored && !memberExists && !optional) return false;
}
// check object literals for too many fields
if (isStruct && fromClassModel.isObjectLiteral()) {
for (HaxeBaseMemberModel member : fromMembers) {
if(toMembers.stream().noneMatch(model -> model.getNamePsi().textMatches(member.getName()))) {
// too many fields
return false;
}
}
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,11 @@ public void testAssignStringToFloat() throws Exception {
doTestNoFixWithWarnings();
}

@Test
public void testAssignStructInit() throws Exception {
doTestNoFixWithWarnings();
}

@Test
public void testAssignStringToDynamic() throws Exception {
doTestNoFixWithWarnings();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package;
@:structInit class MyStruct {
//NOTE: should not show warning about final when in structInit class
final name:String;
var age:Int = 30;
@:optional var address:String;

}

class Test {
public function new() {
// correct
var s1:MyStruct = {name:"name", age:30};
var s2:MyStruct = {name:"name", age:30, address:"address 1"};

// wrong (TODO better error messages)
var <error descr="Incompatible type: {...} should be MyStruct">s3:MyStruct = {name:"name"}</error>; // missing field
var <error descr="Incompatible type: {...} should be MyStruct">s4:MyStruct = {name:"name", age:30, address:"address 1", extra:"field"}</error>; // to many fields

}
}

0 comments on commit a74a8d6

Please sign in to comment.