From f869c76965a4bb4ade2bc080af3100e05723984f Mon Sep 17 00:00:00 2001 From: Frotty Date: Thu, 11 Dec 2025 20:20:43 +0100 Subject: [PATCH 1/5] more bugs --- .../providers/FrameProvider.java | 6 + .../tests/GenericsWithTypeclassesTests.java | 107 ++++++++++++++++++ .../java/tests/wurstscript/tests/StdLib.java | 2 +- 3 files changed, 114 insertions(+), 1 deletion(-) diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/jassinterpreter/providers/FrameProvider.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/jassinterpreter/providers/FrameProvider.java index 06f6a6e51..b268b112f 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/jassinterpreter/providers/FrameProvider.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/jassinterpreter/providers/FrameProvider.java @@ -24,6 +24,10 @@ public IlConstHandle BlzCreateFrameByType(ILconstString typeName, ILconstString return new IlConstHandle("framehandle", new FrameHandle()); } + public IlConstHandle BlzGetFrameByName(ILconstString name, ILconstInt createContext) { + return new IlConstHandle("framehandle", new FrameHandle()); + } + public void BlzFrameSetSize(IlConstHandle frame, ILconstReal width, ILconstReal height) { } @@ -35,4 +39,6 @@ public IlConstHandle ConvertOriginFrameType(ILconstInt i) { return new IlConstHandle("frameType", i.getVal()); } + + } diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/GenericsWithTypeclassesTests.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/GenericsWithTypeclassesTests.java index ad1bb0973..e56c8a4f9 100644 --- a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/GenericsWithTypeclassesTests.java +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/GenericsWithTypeclassesTests.java @@ -1922,6 +1922,113 @@ public void genericClassWithLLModule() { ); } + @Test + public void genericClassWithLLModuleBig() { + testAssertOkLinesWithStdLib(true, + "package test", + "import LinkedListModule", + "", + "class Box", + " use LinkedListModule", + " private T value", + " function setValue(T v)", + " value = v", + " function getValue() returns T", + " return value", + "", + "class OtherBox", + " use LinkedListModule", + " private U v", + " construct(U x)", + " v = x", + " function get() returns U", + " return v", + "init", + " foo()", + "function foo()", + " // Basic sanity", + " let b0 = new Box", + " b0.setValue(42)", + " if b0.getValue() != 42 or b0.prev != null", + " return", + " b0.remove()", + "", + " // Build a 3-element list", + " let b1 = new Box", + " let b2 = new Box", + " let b3 = new Box", + " b1.setValue(1)", + " b2.setValue(2)", + " b3.setValue(3)", + "", + " // Circular next()", + " if b1.getNext() != b2 or b2.getNext() != b3 or b3.getNext() != b1", + " return", + "", + " // Circular prev()", + " if b1.getPrev() != b3 or b2.getPrev() != b1 or b3.getPrev() != b2", + " return", + "", + " // Removal in the middle", + " b2.remove()", + " if b1.getNext() != b3 or b3.getPrev() != b1", + " return", + "", + " // Destroy remaining list", + " destroy b1", + " destroy b3", + "", + " // NEW: Generic OtherBox list", + " let o1 = new OtherBox(10)", + " let o2 = new OtherBox(20)", + " let o3 = new OtherBox(30)", + "", + " if o1.getPrev() != o3 or o3.getNext() != o1", + " return", + "", + " // Removal test in OtherBox list", + " o2.remove()", + " if o1.getNext() != o3 or o3.getPrev() != o1", + " return", + "", + " destroy o1", + " destroy o3", + "", + " testSuccess()", + "endpackage" + ); + } + + @Test + public void genericClassWithLLModule1() { + testAssertOkLinesWithStdLib(true, + "package test", + "import LinkedListModule", + "class Box", + " use LinkedListModule", + "class Box2", + " use LinkedListModule", + "init", + " let b = new Box", + " let b2 = new Box2", + " testSuccess()", + "endpackage" + ); + } + + @Test + public void genericClassStaticAttribute() { + testAssertOkLines(true, + "package test", + "class Box", + " static int count = 1", + "init", + " if Box.count == 1 and Box.count == 1", + " testSuccess()", + "endpackage" + ); + } + } diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/StdLib.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/StdLib.java index 1c68780d8..8406bfd70 100644 --- a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/StdLib.java +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/StdLib.java @@ -23,7 +23,7 @@ public class StdLib { /** * version to use for the tests */ - private final static String version = "6107c40e64fa646a016e8c446026f2f5cf3f2a1e"; + private final static String version = "e6463189f754b8794e59ba9d4ac1a91977c8aaac"; /** * flag so that initialization in only done once From be33e1f8ed13cb9dfbca6fd8576b5e44773906cc Mon Sep 17 00:00:00 2001 From: Frotty Date: Thu, 11 Dec 2025 20:35:26 +0100 Subject: [PATCH 2/5] Update EliminateGenerics.java --- .../translation/imtranslation/EliminateGenerics.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/EliminateGenerics.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/EliminateGenerics.java index e0e4fcb2f..c01c6fd93 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/EliminateGenerics.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/EliminateGenerics.java @@ -208,6 +208,15 @@ private void removeGenericConstructs() { for (ImClass c : prog.getClasses()) { c.getFields().removeIf(f -> isGenericType(f.getType())); } + + // NEW: Remove original generic global variables + prog.getGlobals().removeIf(v -> { + if (globalToClass.containsKey(v)) { + WLogger.info("Removing generic global variable: " + v.getName() + " with type " + v.getType()); + return true; + } + return false; + }); } private void eliminateGenericUses() { From 6260fa93f636a64a40eb2eb13336204e36a7a0ca Mon Sep 17 00:00:00 2001 From: Frotty Date: Fri, 12 Dec 2025 17:27:53 +0100 Subject: [PATCH 3/5] WIP generic updates --- .../parserspec/jass_im.parseq | 1 + .../parserspec/wurstscript.parseq | 1 + .../antlr/de/peeeq/wurstscript/antlr/Wurst.g4 | 8 + .../languageserver/requests/HoverInfo.java | 5 + .../de/peeeq/wurstscript/WurstParser.java | 2 + .../attributes/AttrCallSignature.java | 23 ++- .../wurstscript/attributes/AttrExprType.java | 4 + .../attributes/AttrImplicitParameter.java | 40 ++-- .../attributes/DescriptionHtml.java | 4 + .../wurstscript/attributes/ReadVariables.java | 4 + .../attributes/prettyPrint/PrettyPrinter.java | 4 + .../intermediatelang/ILconstTypeRef.java | 29 +++ .../interpreter/EvaluateExpr.java | 6 + .../interpreter/ProgramState.java | 110 +++++++++-- .../optimizer/FunctionSplitter.java | 5 + .../optimizer/SideEffectAnalyzer.java | 5 + .../antlr/AntlrWurstParseTreeTransformer.java | 18 ++ .../translation/imtojass/Equality.java | 7 + .../translation/imtojass/ExprTranslation.java | 5 + .../translation/imtojass/ImAttrType.java | 4 + .../imtojass/ImToJassTranslator.java | 33 +++- .../imtranslation/EliminateGenerics.java | 53 ++++-- .../imtranslation/ExprTranslation.java | 79 ++++++-- .../translation/imtranslation/ImPrinter.java | 4 + .../imtranslation/ImTranslator.java | 94 ++++++++++ .../lua/translation/ExprTranslation.java | 5 + .../wurstscript/types/CallSignature.java | 27 ++- .../validation/WurstValidator.java | 43 ++++- .../tests/GenericsWithTypeclassesTests.java | 173 +++++++++++++++++- .../wurstscript/tests/WurstScriptTest.java | 26 ++- 30 files changed, 722 insertions(+), 100 deletions(-) create mode 100644 de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/intermediatelang/ILconstTypeRef.java diff --git a/de.peeeq.wurstscript/parserspec/jass_im.parseq b/de.peeeq.wurstscript/parserspec/jass_im.parseq index abb3420dc..22e9f7ad4 100644 --- a/de.peeeq.wurstscript/parserspec/jass_im.parseq +++ b/de.peeeq.wurstscript/parserspec/jass_im.parseq @@ -142,6 +142,7 @@ ImConst = | ImBoolVal(boolean valB) | ImFuncRef(@ignoreForEquality de.peeeq.wurstscript.ast.Element trace, ref ImFunction func) | ImNull(ref ImType type) + | ImTypeRef(@ignoreForEquality de.peeeq.wurstscript.ast.Element trace, ref ImClassType clazz) ImTypeArguments * ImTypeArgument diff --git a/de.peeeq.wurstscript/parserspec/wurstscript.parseq b/de.peeeq.wurstscript/parserspec/wurstscript.parseq index 6f84941a7..ef59edd11 100644 --- a/de.peeeq.wurstscript/parserspec/wurstscript.parseq +++ b/de.peeeq.wurstscript/parserspec/wurstscript.parseq @@ -188,6 +188,7 @@ Expr = | ExprDestroy(@ignoreForEquality de.peeeq.wurstscript.parser.WPos source, Expr destroyedObj) | ExprIfElse(@ignoreForEquality de.peeeq.wurstscript.parser.WPos source, Expr cond, Expr ifTrue, Expr ifFalse) | ExprArrayLength(@ignoreForEquality de.peeeq.wurstscript.parser.WPos source, Expr array) + | ExprTypeRef(@ignoreForEquality de.peeeq.wurstscript.parser.WPos source, TypeExpr typ) ExprMember = ExprMemberVar diff --git a/de.peeeq.wurstscript/src/main/antlr/de/peeeq/wurstscript/antlr/Wurst.g4 b/de.peeeq.wurstscript/src/main/antlr/de/peeeq/wurstscript/antlr/Wurst.g4 index e3681dec8..1e00a5aa5 100644 --- a/de.peeeq.wurstscript/src/main/antlr/de/peeeq/wurstscript/antlr/Wurst.g4 +++ b/de.peeeq.wurstscript/src/main/antlr/de/peeeq/wurstscript/antlr/Wurst.g4 @@ -347,6 +347,9 @@ indexes: '[' expr ']' ; +exprTypeRef + : typeName=ID typeArgsNonEmpty + ; expr: exprPrimary @@ -368,12 +371,14 @@ expr: ; + exprPrimary: exprFunctionCall | exprNewObject | exprClosure | exprStatementsBlock | exprDestroy + | exprTypeRef | varname=ID indexes? | atom=(INT | REAL @@ -438,6 +443,9 @@ stmtBreak:'break'; stmtSkip:'skip'; +typeArgsNonEmpty + : '<' (args+=typeExpr (',' args+=typeExpr)*)? '>' + ; typeArgs: ('<' (args+=typeExpr (',' args+=typeExpr)*)? '>')?; diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/HoverInfo.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/HoverInfo.java index 9b3bb474a..22efca8ec 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/HoverInfo.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/HoverInfo.java @@ -782,6 +782,11 @@ public List> case_VisibilityPublic(VisibilityPublic return string("This element can be used everywhere"); } + @Override + public List> case_ExprTypeRef(ExprTypeRef exprTypeRef) { + return List.of(); + } + @Override public List> case_TopLevelDeclarations(TopLevelDeclarations topLevelDeclarations) { return string("A list of declarations."); diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/WurstParser.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/WurstParser.java index 4d665dd73..13c9f5369 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/WurstParser.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/WurstParser.java @@ -112,7 +112,9 @@ public void syntaxError(@SuppressWarnings("null") Recognizer recognizer, @ parser.removeErrorListeners(); parser.addErrorListener(l); + parser.setBuildParseTree(true); CompilationUnitContext cu = parser.compilationUnit(); // begin parsing at init rule + System.out.println(cu.toStringTree(parser)); if (lexer.getTabWarning() != null) { CompileError warning = lexer.getTabWarning(); diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/AttrCallSignature.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/AttrCallSignature.java index 0f2c80873..96119fc3d 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/AttrCallSignature.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/AttrCallSignature.java @@ -5,23 +5,30 @@ import de.peeeq.wurstscript.ast.ExprMemberMethod; import de.peeeq.wurstscript.ast.ExprNewObject; import de.peeeq.wurstscript.types.CallSignature; +import de.peeeq.wurstscript.types.WurstType; public class AttrCallSignature { public static CallSignature calculate(ExprFunctionCall c) { - Expr receiver = null; - if (c.attrImplicitParameter() instanceof Expr) { - receiver = (Expr) c.attrImplicitParameter(); - } - return new CallSignature(receiver, c.getArgs()); + Expr receiver = (c.attrImplicitParameter() instanceof Expr) ? (Expr) c.attrImplicitParameter() : null; + WurstType hint = receiver != null ? receiver.attrTyp() : null; + return new CallSignature(receiver, hint, c.getArgs()); } public static CallSignature calculate(ExprMemberMethod c) { - return new CallSignature(c.attrImplicitParameter(), c.getArgs()); + // Always keep the instantiated left type as hint (e.g. OtherBox) + WurstType hint = c.getLeft().attrTyp(); + + if (c.getLeft().attrTyp().isStaticRef()) { + // static ref like OtherBox.staticItr() + return new CallSignature(null, hint, c.getArgs()); + } + + // instance call: keep real receiver expression AND hint + return new CallSignature(c.attrImplicitParameter(), hint, c.getArgs()); } public static CallSignature calculate(ExprNewObject c) { - return new CallSignature(null, c.getArgs()); + return new CallSignature((Expr) null, null, c.getArgs()); } - } diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/AttrExprType.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/AttrExprType.java index 817b74a47..18f361b36 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/AttrExprType.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/AttrExprType.java @@ -591,4 +591,8 @@ public static WurstType calculate(ExprArrayLength exprArrayLength) { exprArrayLength.addError(".length is only valid on arrays."); return de.peeeq.wurstscript.types.WurstTypeUnknown.instance(); } + + public static WurstType calculate(ExprTypeRef e) { + return e.getTyp().attrTyp(); + } } diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/AttrImplicitParameter.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/AttrImplicitParameter.java index c9b3720b4..2e72ff3fd 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/AttrImplicitParameter.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/AttrImplicitParameter.java @@ -9,7 +9,7 @@ public class AttrImplicitParameter { public static OptExpr getImplicitParameter(ExprMemberVar e) { - Expr result = getImplicitParameterUsingLeft(e); + Expr result = getImplicitParameterUsingLeft(e, true); // keep static refs if (result == null) { return getImplicitParamterCaseNormalVar(e); } else { @@ -18,7 +18,7 @@ public static OptExpr getImplicitParameter(ExprMemberVar e) { } public static OptExpr getImplicitParameter(ExprMemberArrayVar e) { - Expr result = getImplicitParameterUsingLeft(e); + Expr result = getImplicitParameterUsingLeft(e, true); // keep static refs if (result == null) { return getImplicitParamterCaseNormalVar(e); } else { @@ -40,7 +40,7 @@ public static OptExpr getImplicitParameter(ExprFunctionCall e) { } public static OptExpr getImplicitParameter(ExprMemberMethod e) { - Expr result = getImplicitParameterUsingLeft(e); + Expr result = getImplicitParameterUsingLeft(e, false); if (result == null) { return getImplicitParameterCaseNormalFunctionCall(e); } else { @@ -55,14 +55,19 @@ public static OptExpr getImplicitParameter(ExprMemberMethod e) { } } - private static @Nullable Expr getImplicitParameterUsingLeft(HasReceiver e) { - if (e.getLeft().attrTyp().isStaticRef()) { - // we have a static ref like Math.sqrt() - // this will be handled like if we just have sqrt() - // if we have an implicit parameter depends on whether sqrt is static or not + private static @Nullable Expr getImplicitParameterUsingLeft(HasReceiver e, boolean keepStaticRef) { + Expr left = e.getLeft(); + + if (left.attrTyp().isStaticRef()) { + // Only keep typed refs like Box so we can specialize generics. + // Plain identifiers like BoxInt must NOT become runtime receivers. + if (keepStaticRef && left instanceof ExprTypeRef) { + return left; + } return null; } - return e.getLeft(); + + return left; } private static OptExpr getImplicitParameterCaseNormalFunctionCall(FunctionCall e) { @@ -71,12 +76,19 @@ private static OptExpr getImplicitParameterCaseNormalFunctionCall(FunctionCall e } static OptExpr getFunctionCallImplicitParameter(FunctionCall e, FuncLink calledFunc, boolean showError) { - if (e instanceof HasReceiver) { - HasReceiver hasReceiver = (HasReceiver) e; - Expr res = getImplicitParameterUsingLeft(hasReceiver); - if (res != null) { - return res; + if (e instanceof HasReceiver hasReceiver) { + Expr left = hasReceiver.getLeft(); + + if (left.attrTyp().isStaticRef()) { + // Keep ONLY typed static refs to propagate type args (Box.foo()). + if (left instanceof ExprTypeRef) { + return left; // type-only receiver + } + return Ast.NoExpr(); // plain BoxInt.foo() has no receiver } + + // normal dynamic receiver + return left; } if (calledFunc == null) { return Ast.NoExpr(); diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/DescriptionHtml.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/DescriptionHtml.java index 0f83f666f..04581414e 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/DescriptionHtml.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/DescriptionHtml.java @@ -425,4 +425,8 @@ public static String description(NoTypeParamConstraints noTypeParamConstraints) public static String description(ExprArrayLength exprArrayLength) { return "Get the length of an array."; } + + public static String description(ExprTypeRef exprTypeRef) { + return null; + } } diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/ReadVariables.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/ReadVariables.java index e734b3143..50ca1a713 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/ReadVariables.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/ReadVariables.java @@ -205,4 +205,8 @@ public static ImmutableList calculate(ExprIfElse e) { public static ImmutableList calculate(ExprArrayLength exprArrayLength) { return ImmutableList.emptyList(); } + + public static ImmutableList calculate(ExprTypeRef exprTypeRef) { + return ImmutableList.emptyList(); + } } diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/prettyPrint/PrettyPrinter.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/prettyPrint/PrettyPrinter.java index 8e908c913..5445d851e 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/prettyPrint/PrettyPrinter.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/prettyPrint/PrettyPrinter.java @@ -1432,4 +1432,8 @@ public static void prettyPrint(ExprArrayLength exprArrayLength, Spacer spacer, S exprArrayLength.getArray().prettyPrint(spacer, sb, indent); sb.append(".length"); } + + public static void prettyPrint(ExprTypeRef exprTypeRef, Spacer spacer, StringBuilder sb, int indent) { + + } } diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/intermediatelang/ILconstTypeRef.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/intermediatelang/ILconstTypeRef.java new file mode 100644 index 000000000..04292bd9d --- /dev/null +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/intermediatelang/ILconstTypeRef.java @@ -0,0 +1,29 @@ +package de.peeeq.wurstscript.intermediatelang; + +import de.peeeq.wurstscript.jassIm.ImClassType; + +public final class ILconstTypeRef implements ILconst { + private final ImClassType type; // usually + + public ILconstTypeRef(ImClassType type) { + this.type = type; + } + + public ImClassType getType() { + return type; + } + + @Override + public String print() { + return ""; + } + + @Override + public boolean isEqualTo(ILconst other) { + return false; + } + + @Override public String toString() { + return "type(" + type + ")"; + } +} diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/intermediatelang/interpreter/EvaluateExpr.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/intermediatelang/interpreter/EvaluateExpr.java index 8c1c0f33d..738ea3af9 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/intermediatelang/interpreter/EvaluateExpr.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/intermediatelang/interpreter/EvaluateExpr.java @@ -490,4 +490,10 @@ public static ILconst eval(ImCast imCast, ProgramState globalState, LocalState l } return res; } + + public static ILconst eval(ImTypeRef imTypeRef, ProgramState globalState, LocalState localState) { + // Resolve any type vars based on current substitutions: + ImType resolved = globalState.resolveType(imTypeRef.attrTyp()); + return new ILconstTypeRef((ImClassType) resolved); + } } diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/intermediatelang/interpreter/ProgramState.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/intermediatelang/interpreter/ProgramState.java index 19a4dfcd7..015f4e558 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/intermediatelang/interpreter/ProgramState.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/intermediatelang/interpreter/ProgramState.java @@ -405,6 +405,65 @@ public ILStackFrame next() { } private final Object2ObjectOpenHashMap genericStaticVals = new Object2ObjectOpenHashMap<>(); + private final Object2ObjectOpenHashMap genericStaticArrays = new Object2ObjectOpenHashMap<>(); + + private final Object2ObjectOpenHashMap genericStaticOwnerCache = new Object2ObjectOpenHashMap<>(); + + + private @Nullable ImClass tryGetGenericOwnerClassForGlobal(ImVar v) { + if (!v.isGlobal()) return null; + + String name = v.getName(); + int underscore = name.indexOf('_'); + if (underscore <= 0) return null; + + String className = name.substring(0, underscore); + + ImClass cached = genericStaticOwnerCache.get(className); + if (cached != null) { + // could be non-generic too; we store it anyway and check below + return cached.getTypeVariables().isEmpty() ? null : cached; + } + + // lookup class by name (fast enough; cached afterwards) + for (ImClass c : prog.getClasses()) { + if (c.getName().equals(className)) { + genericStaticOwnerCache.put(className, c); + return c.getTypeVariables().isEmpty() ? null : c; + } + } + + // remember "not found" as null by storing a dummy? keep simple: don't cache misses + return null; + } + + private @Nullable ILStackFrame topFrameWithReceiver() { + ILStackFrame top = stackFrames.peek(); + return (top != null && top.receiver != null) ? top : null; + } + + private @Nullable String genericStaticKey(ImVar v) { + // Old behavior: also treat "static T ..." lowered to global of ImTypeVarRef as generic + boolean isTypeVarStatic = v.getType() instanceof ImTypeVarRef; + + // New behavior: treat ANY global that belongs to a generic class as generic-static + ImClass genericOwner = tryGetGenericOwnerClassForGlobal(v); + + if (!isTypeVarStatic && genericOwner == null) { + return null; + } + + ILStackFrame top = topFrameWithReceiver(); + if (top == null) { + // No receiver context => cannot pick instantiation; fall back to shared global semantics + return null; + } + + // Use the receiver's *concrete* type (already concrete for objects) + ImClassType recvType = top.receiver.getType(); + return v.getName() + "@" + instantiationKey(recvType); + } + public String instantiationKey(ImClassType ct) { @@ -425,29 +484,21 @@ public String instantiationKey(ImClassType ct) { @Override public void setVal(ImVar v, ILconst val) { - if (v.isGlobal() && v.getType() instanceof ImTypeVarRef) { - ILStackFrame top = stackFrames.peek(); - if (top != null && top.receiver != null) { - ImTypeArguments tas = top.receiver.getType().getTypeArguments(); - ImType resolved = resolveType(tas.get(0).getType()); // <<< resolve - String s = v.getName() + resolved; - genericStaticVals.put(s, val); - return; - } + String key = genericStaticKey(v); + if (key != null) { + genericStaticVals.put(key, val); + return; } super.setVal(v, val); } + @Override public @Nullable ILconst getVal(ImVar v) { - if (v.isGlobal() && v.getType() instanceof ImTypeVarRef) { - System.out.println("looking for generic static var " + v); - ILStackFrame top = stackFrames.peek(); - if (top != null && top.receiver != null) { - ImTypeArguments tas = top.receiver.getType().getTypeArguments(); - ImType resolved = resolveType(tas.get(0).getType()); // <<< resolve - String s = v.getName() + resolved; - return genericStaticVals.get(s); - } + String key = genericStaticKey(v); + if (key != null) { + ILconst r = genericStaticVals.get(key); + if (r != null) return r; + // If never written, fall through to default global storage (which might have init/defaults) } return super.getVal(v); } @@ -461,6 +512,27 @@ public boolean isCompiletime() { @Override protected ILconstArray getArray(ImVar v) { + String key = genericStaticKey(v); + if (key != null) { + ILconstArray r = genericStaticArrays.get(key); + if (r != null) return r; + + r = createArrayConstantFromType(v.getType()); + genericStaticArrays.put(key, r); + + // Initialize from globalInits only once per instantiation + List inits = prog.getGlobalInits().get(v); + if (inits != null && !inits.isEmpty()) { + final LocalState ls = EMPTY_LOCAL_STATE; + for (int i = 0; i < inits.size(); i++) { + ILconst val = inits.get(i).getRight().evaluate(this, ls); + r.set(i, val); + } + } + return r; + } + + // old behavior for non-generic statics Object2ObjectOpenHashMap arrayValues = ensureArrayValues(); ILconstArray r = arrayValues.get(v); if (r != null) return r; @@ -468,10 +540,8 @@ protected ILconstArray getArray(ImVar v) { r = createArrayConstantFromType(v.getType()); arrayValues.put(v, r); - // Initialize from globalInits only once List inits = prog.getGlobalInits().get(v); if (inits != null && !inits.isEmpty()) { - // evaluate with a reusable local state to avoid per-init allocations final LocalState ls = EMPTY_LOCAL_STATE; for (int i = 0; i < inits.size(); i++) { ILconst val = inits.get(i).getRight().evaluate(this, ls); diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/intermediatelang/optimizer/FunctionSplitter.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/intermediatelang/optimizer/FunctionSplitter.java index 55a91330d..68b7e5b46 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/intermediatelang/optimizer/FunctionSplitter.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/intermediatelang/optimizer/FunctionSplitter.java @@ -109,6 +109,11 @@ public Integer case_ImDealloc(ImDealloc s) { return 10 + estimateFuel(s.getObj()); } + @Override + public Integer case_ImTypeRef(ImTypeRef imTypeRef) { + return 0; + } + @Override public Integer case_ImBoolVal(ImBoolVal s) { return 1; diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/intermediatelang/optimizer/SideEffectAnalyzer.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/intermediatelang/optimizer/SideEffectAnalyzer.java index 0b2db5ca2..c3d9568cd 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/intermediatelang/optimizer/SideEffectAnalyzer.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/intermediatelang/optimizer/SideEffectAnalyzer.java @@ -76,6 +76,11 @@ public Boolean case_ImDealloc(ImDealloc imDealloc) { return true; } + @Override + public Boolean case_ImTypeRef(ImTypeRef imTypeRef) { + return false; + } + @Override public Boolean case_ImMemberAccess(ImMemberAccess e) { return quickcheckHasSideeffects(e.getReceiver()); diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/parser/antlr/AntlrWurstParseTreeTransformer.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/parser/antlr/AntlrWurstParseTreeTransformer.java index bbbd03425..b06153e07 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/parser/antlr/AntlrWurstParseTreeTransformer.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/parser/antlr/AntlrWurstParseTreeTransformer.java @@ -789,6 +789,22 @@ private ExprDestroy transformExprDestroy(ExprDestroyContext e) { return Ast.ExprDestroy(source(e), transformExpr(e.expr())); } + private ExprTypeRef transformExprTypeRef(ExprTypeRefContext e) { + WPos src = source(e); + + // Build TypeExprSimple("Box", ) + String typeName = rawText(e.typeName); + + TypeExprList typeArgs = Ast.TypeExprList(); + for (TypeExprContext t : e.typeArgsNonEmpty().args) { + typeArgs.add(transformTypeExpr(t)); + } + + TypeExpr typ = Ast.TypeExprSimple(src, Ast.NoTypeExpr(), typeName, typeArgs); + return Ast.ExprTypeRef(src, typ); + } + + private StmtReturn transformReturn(StmtReturnContext s) { OptExpr r = transformOptionalExpr(s.expr()); if (r instanceof ExprEmpty) { @@ -1221,6 +1237,8 @@ private Expr transformExprPrimary(ExprPrimaryContext e) { return transformExprFuncRef(e.exprFuncRef()); } else if (e.exprDestroy() != null) { return transformExprDestroy(e.exprDestroy()); + } else if (e.exprTypeRef() != null) { + return transformExprTypeRef(e.exprTypeRef()); } // TODO Auto-generated method stub throw error(e, "not implemented " + text(e)); diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtojass/Equality.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtojass/Equality.java index 9e25f7055..38216a0cd 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtojass/Equality.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtojass/Equality.java @@ -48,4 +48,11 @@ public static boolean equalValue(ImStringVal v, ImConst other) { return false; } + public static boolean equalValue(ImTypeRef imTypeRef, ImConst other) { + if (!(other instanceof ImTypeRef)) { + return false; + } + ImTypeRef o = (ImTypeRef) other; + return imTypeRef.getClazz().equalsType(o.getClazz()); + } } diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtojass/ExprTranslation.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtojass/ExprTranslation.java index 9613ec3dd..e1b1accf6 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtojass/ExprTranslation.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtojass/ExprTranslation.java @@ -167,4 +167,9 @@ public static JassExpr translate(ImTypeVarDispatch e, ImToJassTranslator transla public static JassExpr translate(ImCast imCast, ImToJassTranslator translator) { return imCast.getExpr().translate(translator); } + + public static JassExpr translate(ImTypeRef imTypeRef, ImToJassTranslator translator) { + throw new RuntimeException("ImTypeRef must be eliminated before JASS translation: " + + imTypeRef.attrTrace()); + } } diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtojass/ImAttrType.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtojass/ImAttrType.java index 1c485bb6c..698a5dc90 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtojass/ImAttrType.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtojass/ImAttrType.java @@ -232,4 +232,8 @@ public static ImType getType(ImTypeVarDispatch e) { public static ImType getType(ImCast imCast) { return imCast.getToType(); } + + public static ImType getType(ImTypeRef imTypeRef) { + return JassIm.ImAnyType(); + } } diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtojass/ImToJassTranslator.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtojass/ImToJassTranslator.java index af32c53af..05632456b 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtojass/ImToJassTranslator.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtojass/ImToJassTranslator.java @@ -202,8 +202,11 @@ JassVar getJassVarFor(ImVar v) { result = JassAst.JassArrayVar(type, name); } else { if (isGlobal(v) && v.getType() instanceof ImSimpleType) { - JassExpr initialVal = ImHelper.defaultValueForType((ImSimpleType) v.getType()).translate(this); - result = JassAst.JassInitializedVar(type, name, initialVal, v.getIsBJ()); + JassExpr init = tryGetScalarGlobalInit(v); + if (init == null) { + init = ImHelper.defaultValueForType((ImSimpleType) v.getType()).translate(this); + } + result = JassAst.JassInitializedVar(type, name, init, v.getIsBJ()); } else { result = JassAst.JassSimpleVar(type, name); } @@ -216,6 +219,32 @@ JassVar getJassVarFor(ImVar v) { return result; } + private @Nullable JassExpr tryGetScalarGlobalInit(ImVar v) { + List inits = imProg.getGlobalInits().get(v); + if (inits == null || inits.isEmpty()) return null; + if (inits.size() != 1) return null; + + ImSet s = inits.get(0); + + // Must be: set = + if (!(s.getLeft() instanceof ImVarAccess)) return null; + ImVarAccess va = (ImVarAccess) s.getLeft(); + if (va.getVar() != v) return null; + + ImExpr rhs = s.getRight(); + + // Only allow JASS-legal constant initializers + if (rhs instanceof ImIntVal || + rhs instanceof ImRealVal || + rhs instanceof ImStringVal || + rhs instanceof ImBoolVal || + rhs instanceof ImNull) { + return rhs.translate(this); + } + + return null; + } + private String jassifyName(String name) { name = filterInvalidSymbols(name); if (RestrictedCompressedNames.contains(name) || name.startsWith("_")) { diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/EliminateGenerics.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/EliminateGenerics.java index c01c6fd93..a157f11c9 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/EliminateGenerics.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/EliminateGenerics.java @@ -187,12 +187,9 @@ private void identifyGenericGlobals() { ImClass owningClass = classMap.get(potentialClassName); if (owningClass != null && !owningClass.getTypeVariables().isEmpty()) { - // This global belongs to a generic class - if (containsTypeVariable(global.getType())) { - globalToClass.put(global, owningClass); - WLogger.info("Identified generic global: " + varName + " of type " + global.getType() + - " belonging to class " + owningClass.getName()); - } + globalToClass.put(global, owningClass); + WLogger.info("Identified generic global: " + varName + " of type " + global.getType() + + " belonging to class " + owningClass.getName()); } } } @@ -209,14 +206,16 @@ private void removeGenericConstructs() { c.getFields().removeIf(f -> isGenericType(f.getType())); } - // NEW: Remove original generic global variables - prog.getGlobals().removeIf(v -> { - if (globalToClass.containsKey(v)) { - WLogger.info("Removing generic global variable: " + v.getName() + " with type " + v.getType()); - return true; - } - return false; - }); +// NEW: Remove original generic global variables +// prog.getGlobals().removeIf(v -> { +// if (globalToClass.containsKey(v)) { +// WLogger.info("Removing generic global variable: " + v.getName() + " with type " + v.getType()); +// prog.getGlobalInits().remove(v); +// return true; +// } +// return false; +// }); + prog.clearAttributes(); } private void eliminateGenericUses() { @@ -500,6 +499,32 @@ private void createSpecializedGlobals(ImClass originalClass, GenericTypes generi // Add to program globals prog.getGlobals().add(specializedGlobal); + List inits = prog.getGlobalInits().get(originalGlobal); + if (inits != null && !inits.isEmpty()) { + List newInits = new ArrayList<>(inits.size()); + for (ImSet s : inits) { + ImSet c = s.copy(); + + // retarget left side to the specialized global + if (c.getLeft() instanceof ImVarAccess) { + ImVarAccess va = (ImVarAccess) c.getLeft(); + if (va.getVar() == originalGlobal) { + va.setVar(specializedGlobal); + } + } else if (c.getLeft() instanceof ImVarArrayAccess) { + ImVarArrayAccess vaa = (ImVarArrayAccess) c.getLeft(); + if (vaa.getVar() == originalGlobal) { + vaa.setVar(specializedGlobal); + } + } + + // specialize any types inside the init statement (right side, indices, null types, etc.) + rewriteGenerics(c, generics, typeVars); + + newInits.add(c); + } + prog.getGlobalInits().put(specializedGlobal, newInits); + } // Track the specialization specializedGlobals.put(originalGlobal, generics, specializedGlobal); diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/ExprTranslation.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/ExprTranslation.java index be92410dc..bb793487c 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/ExprTranslation.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/ExprTranslation.java @@ -113,7 +113,7 @@ static ImExpr wrapLua(Element trace, ImTranslator t, ImExpr translated, WurstTyp ensureType = t.ensureRealFunc; break; } - if(ensureType != null) { + if (ensureType != null) { return ImFunctionCall(trace, ensureType, ImTypeArguments(), JassIm.ImExprs(translated), false, CallType.NORMAL); } } @@ -148,7 +148,7 @@ static ImExpr wrapTranslation(Element trace, ImTranslator t, ImExpr translated, return wrapLua(trace, t, translated, actualType); } else if (fromIndex != null) { // System.out.println(" --> fromIndex"); - if(t.isLuaTarget()) { + if (t.isLuaTarget()) { translated = ImFunctionCall(trace, t.ensureIntFunc, ImTypeArguments(), JassIm.ImExprs(translated), false, CallType.NORMAL); } // no ensure type necessary here, because the fromIndex function is already type safe @@ -172,14 +172,14 @@ public static ImExpr translateIntern(ExprBinary e, ImTranslator t, ImFunction f) if (op == WurstOperator.DIV_REAL) { if (Utils.isJassCode(e)) { if (e.getLeft().attrTyp().isSubtypeOf(WurstTypeInt.instance(), e) - && e.getRight().attrTyp().isSubtypeOf(WurstTypeInt.instance(), e)) { + && e.getRight().attrTyp().isSubtypeOf(WurstTypeInt.instance(), e)) { // in jass when we have int1 / int2 this actually means int1 // div int2 op = WurstOperator.DIV_INT; } } else { if (e.getLeft().attrTyp().isSubtypeOf(WurstTypeInt.instance(), e) - && e.getRight().attrTyp().isSubtypeOf(WurstTypeInt.instance(), e)) { + && e.getRight().attrTyp().isSubtypeOf(WurstTypeInt.instance(), e)) { // we want a real division but have 2 ints so we need to // multiply with 1.0 // TODO is this really needed or handled in IM->Jass @@ -261,7 +261,21 @@ private static ImExpr translateNameDef(NameRef e, ImTranslator t, ImFunction f) if (e.attrImplicitParameter() instanceof Expr) { // we have implicit parameter // e.g. "someObject.someField" + Expr implicitParam = (Expr) e.attrImplicitParameter(); + if (implicitParam instanceof ExprTypeRef) { + WurstType tpe = ((ExprTypeRef) implicitParam).attrTyp(); + if (tpe instanceof WurstTypeClassOrInterface wtc && wtc.isStaticRef()) { + ImVar spec = t.getSpecializedStaticVar(varDef, wtc); + // handle array index the same way as direct var access: + if (e instanceof AstElementWithIndexes) { + ImExpr index = ((AstElementWithIndexes) e).getIndexes().get(0).imTranslateExpr(t, f); + return ImVarArrayAccess(e, spec, ImExprs(index)); + } + return ImVarAccess(spec); + } + } + if (implicitParam.attrTyp() instanceof WurstTypeTuple) { WurstTypeTuple tupleType = (WurstTypeTuple) implicitParam.attrTyp(); @@ -322,10 +336,10 @@ private static ImExpr translateTupleSelection(ImTranslator t, ImFunction f, Expr ImVar v = ImVar(left.attrTrace(), left.attrTyp(), "temp_tuple", false); f.getLocals().add(v); return JassIm.ImStatementExpr( - JassIm.ImStmts( - ImSet(left.attrTrace(), ImVarAccess(v), left) - ), - ImTupleSelection(ImVarAccess(v), tupleIndex) + JassIm.ImStmts( + ImSet(left.attrTrace(), ImVarAccess(v), left) + ), + ImTupleSelection(ImVarAccess(v), tupleIndex) ); } } @@ -440,7 +454,7 @@ public static ImExpr translateIntern(FunctionCall e, ImTranslator t, ImFunction private static ImExpr translateFunctionCall(FunctionCall e, ImTranslator t, ImFunction f, boolean returnReveiver) { if (e.getFuncName().equals("getStackTraceString") && e.attrImplicitParameter() instanceof NoExpr - && e.getArgs().size() == 0) { + && e.getArgs().size() == 0) { // special built-in error function return JassIm.ImGetStackTrace(); } @@ -454,8 +468,8 @@ private static ImExpr translateFunctionCall(FunctionCall e, ImTranslator t, ImFu } if (e.getFuncName().equals("compiletime") - && e.attrImplicitParameter() instanceof NoExpr - && e.getArgs().size() == 1) { + && e.attrImplicitParameter() instanceof NoExpr + && e.getArgs().size() == 1) { // special compiletime-expression return JassIm.ImCompiletimeExpr(e, e.getArgs().get(0).imTranslateExpr(t, f), t.getCompiletimeExpressionsOrder(e)); } @@ -508,7 +522,13 @@ private static ImExpr translateFunctionCall(FunctionCall e, ImTranslator t, ImFu dynamicDispatch = false; } - ImExpr receiver = leftExpr == null ? null : leftExpr.imTranslateExpr(t, f); + // IMPORTANT: ExprTypeRef must never become a runtime receiver. + // We still keep it in attrFunctionSignature() / type binding, but it produces no ImExpr. + ImExpr receiver = null; + if (leftExpr != null && !(leftExpr instanceof ExprTypeRef)) { + receiver = leftExpr.imTranslateExpr(t, f); + } + ImExprs imArgs = translateExprs(arguments, t, f); if (calledFunc instanceof TupleDef) { @@ -521,25 +541,31 @@ private static ImExpr translateFunctionCall(FunctionCall e, ImTranslator t, ImFu if (returnReveiver) { if (leftExpr == null) throw new Error("impossible"); + + if (leftExpr instanceof ExprTypeRef) { + // cannot "return receiver" for type-only receivers + throw new CompileError(e, "Cannot return receiver for static type reference call."); + } + tempVar = JassIm.ImVar(leftExpr, leftExpr.attrTyp().imTranslateType(t), "receiver", false); f.getLocals().add(tempVar); stmts = JassIm.ImStmts(ImSet(e, ImVarAccess(tempVar), receiver)); receiver = JassIm.ImVarAccess(tempVar); } - - ImExpr call; if (dynamicDispatch) { ImMethod method = t.getMethodFor((FuncDef) calledFunc); - ImTypeArguments typeArguments = getFunctionCallTypeArguments(t, e.attrFunctionSignature(), e, method.getImplementation().getTypeVariables()); + ImTypeArguments typeArguments = + getFunctionCallTypeArguments(t, e.attrFunctionSignature(), e, method.getImplementation().getTypeVariables()); call = ImMethodCall(e, method, typeArguments, receiver, imArgs, false); } else { ImFunction calledImFunc = t.getFuncFor(calledFunc); if (receiver != null) { imArgs.add(0, receiver); } - ImTypeArguments typeArguments = getFunctionCallTypeArguments(t, e.attrFunctionSignature(), e, calledImFunc.getTypeVariables()); + ImTypeArguments typeArguments = + getFunctionCallTypeArguments(t, e.attrFunctionSignature(), e, calledImFunc.getTypeVariables()); call = ImFunctionCall(e, calledImFunc, typeArguments, imArgs, false, CallType.NORMAL); } @@ -724,6 +750,19 @@ public static ImLExpr translateLvalue(LExpr e, ImTranslator t, ImFunction f) { // e.g. "someObject.someField" Expr implicitParam = (Expr) e.attrImplicitParameter(); + if (implicitParam instanceof ExprTypeRef) { + WurstType tpe = ((ExprTypeRef) implicitParam).attrTyp(); + if (tpe instanceof WurstTypeClassOrInterface wtc && wtc.isStaticRef()) { + ImVar spec = t.getSpecializedStaticVar(varDef, wtc); + // handle array index the same way as direct var access: + if (e instanceof AstElementWithIndexes) { + ImExpr index = ((AstElementWithIndexes) e).getIndexes().get(0).imTranslateExpr(t, f); + return ImVarArrayAccess(e, spec, ImExprs(index)); + } + return ImVarAccess(spec); + } + } + if (implicitParam.attrTyp() instanceof WurstTypeTuple) { WurstTypeTuple tupleType = (WurstTypeTuple) implicitParam.attrTyp(); if (e instanceof ExprMemberVar && ((ExprMemberVar) e).getLeft() instanceof LExpr) { @@ -776,6 +815,14 @@ public static ImExpr translate(ExprArrayLength exprArrayLength, ImTranslator tra return JassIm.ImIntVal(0); } + public static ImExpr translate(ExprTypeRef e, ImTranslator translator, ImFunction f) { + // This must never be evaluated as a value. + throw new CompileError( + e.attrSource(), + "Type references cannot be used as runtime expressions." + ); + } + // public static ImLExpr translateLvalue(ExprVarArrayAccess e, ImTranslator translator, ImFunction f) { // NameDef nameDef = e.tryGetNameDef(); // if (nameDef instanceof VarDef) { diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/ImPrinter.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/ImPrinter.java index c4afd7391..c3ce22450 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/ImPrinter.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/ImPrinter.java @@ -593,4 +593,8 @@ public static void print(ImCast e, Appendable sb, int indent) { public static void print(ImAnyType at, Appendable sb, int indent) { append(sb, "any"); } + + public static void print(ImTypeRef imTypeRef, Appendable sb, int indent) { + // TODO + } } diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/ImTranslator.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/ImTranslator.java index 04e03306e..6005b8944 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/ImTranslator.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/ImTranslator.java @@ -66,6 +66,24 @@ public class ImTranslator { private final Set translatedPackages = new ObjectLinkedOpenHashSet<>(); private final Set translatedClasses = new ObjectLinkedOpenHashSet<>(); + private static final class StaticSpecKey { + final VarDef varDef; + final List typeArgs; // already translated + StaticSpecKey(VarDef varDef, List typeArgs) { + this.varDef = varDef; + this.typeArgs = typeArgs; + } + @Override public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof StaticSpecKey k)) return false; + return varDef == k.varDef && Objects.equals(typeArgs, k.typeArgs); + } + @Override public int hashCode() { + return System.identityHashCode(varDef) * 31 + typeArgs.hashCode(); + } + } + + private final Map specializedStaticVars = new Object2ObjectLinkedOpenHashMap<>(); private final Map varMap = new Object2ObjectLinkedOpenHashMap<>(); @@ -175,6 +193,82 @@ private void calculateCompiletimeOrder() { } } + public ImVar getSpecializedStaticVar(VarDef staticVar, WurstTypeClassOrInterface staticRefType) { + // base im var (type, bj, etc.) + ImVar base = getVarFor(staticVar); + + // translate type args of the *static ref* (Box / Box) + List args = new ArrayList<>(); + for (WurstType a : staticRefType.getTypeParameters()) { // adjust if your API name differs + args.add(a.imTranslateType(this)); + } + + StaticSpecKey key = new StaticSpecKey(staticVar, args); + ImVar existing = specializedStaticVars.get(key); + if (existing != null) { + return existing; + } + + // name: __T_ + String mangled = args.stream() + .map(ImType::translateType) // or your own stable mangle + .map(s -> s.replaceAll("[^A-Za-z0-9_]", "_")) + .collect(Collectors.joining("_")); + String newName = base.getName() + "__" + mangled; + + ImVar g = JassIm.ImVar(staticVar, base.getType(), newName, base.getIsBJ()); + addGlobal(g); + + cloneGlobalInits(base, g); + + specializedStaticVars.put(key, g); + return g; + } + + private void cloneGlobalInits(ImVar from, ImVar to) { + List base = imProg.getGlobalInits().get(from); + if (base == null || base.isEmpty()) { + return; + } + + List cloned = new ArrayList<>(base.size()); + for (ImSet s : base) { + ImLExpr left = s.getLeft(); + ImLExpr newLeft; + + if (left instanceof ImVarAccess va && va.getVar() == from) { + newLeft = JassIm.ImVarAccess(to); + + } else if (left instanceof ImVarArrayAccess aa && aa.getVar() == from) { + // copy indexes (avoid re-parenting issues) + ImExprs idx = JassIm.ImExprs(); + for (ImExpr e : aa.getIndexes()) { + idx.add((ImExpr) e.copy()); + } + newLeft = JassIm.ImVarArrayAccess(s.getTrace(), to, idx); + + } else { + // unexpected pattern; skip + continue; + } + + ImExpr newRight = (ImExpr) s.getRight().copy(); + ImSet ns = JassIm.ImSet(s.getTrace(), newLeft, newRight); + cloned.add(ns); + + // insert into same statement list right after original init + if (s.getParent() instanceof ImStmts stmts) { + int i = stmts.indexOf(s); + stmts.add(i + 1, ns); + } else { + // fallback: if no parent, put into global init (keeps correctness, maybe different order) + getGlobalInitFunc().getBody().add(ns); + } + } + + imProg.getGlobalInits().put(to, cloned); + } + private void calculateCompiletimeOrder_walk(WPackage p, Set visited) { if (!visited.add(p)) { return; diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/lua/translation/ExprTranslation.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/lua/translation/ExprTranslation.java index 808880eb5..83960befc 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/lua/translation/ExprTranslation.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/lua/translation/ExprTranslation.java @@ -158,6 +158,11 @@ public static LuaExpr translate(ImOperatorCall e, LuaTranslator tr) { throw new Error("not implemented: " + e); } + public static LuaExpr translate(ImTypeRef imTypeRef, LuaTranslator tr) { + throw new RuntimeException("ImTypeRef must be eliminated before Lua translation: " + + imTypeRef.attrTrace()); + } + static class TupleFunc { final ImTupleType tupleType; final LuaFunction func; diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/types/CallSignature.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/types/CallSignature.java index 41ad2bdeb..d60c1da3c 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/types/CallSignature.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/types/CallSignature.java @@ -9,36 +9,33 @@ public class CallSignature { private final @Nullable Expr receiver; + private final @Nullable WurstType receiverTypeHint; // <-- NEW private final List arguments; - public CallSignature(@Nullable OptExpr optExpr, List arguments) { - if (optExpr instanceof Expr) { - this.receiver = (Expr) optExpr; - } else { - this.receiver = null; - } + public CallSignature(@Nullable Expr receiver, @Nullable WurstType receiverTypeHint, List arguments) { + this.receiver = receiver; + this.receiverTypeHint = receiverTypeHint; this.arguments = arguments; } - public List getArguments() { - return arguments; + public CallSignature(@Nullable OptExpr optExpr, @Nullable WurstType receiverTypeHint, List arguments) { + this((optExpr instanceof Expr) ? (Expr) optExpr : null, receiverTypeHint, arguments); } - public @Nullable Expr getReceiver() { - return receiver; - } + public @Nullable Expr getReceiver() { return receiver; } + public @Nullable WurstType getReceiverTypeHint() { return receiverTypeHint; } // <-- NEW + public List getArguments() { return arguments; } public void checkSignatureCompatibility(FunctionSignature sig, String funcName, Element pos) { - if (sig.isEmpty()) { - return; - } + if (sig.isEmpty()) return; + Expr l_receiver = receiver; if (l_receiver != null) { if (sig.getReceiverType() == null) { l_receiver.addError("No receiver expected for function " + funcName + "."); } else if (!l_receiver.attrTyp().isSubtypeOf(sig.getReceiverType(), l_receiver)) { l_receiver.addError("Incompatible receiver type at call to function " + funcName + ".\n" + - "Found " + l_receiver.attrTyp() + " but expected " + sig.getReceiverType()); + "Found " + l_receiver.attrTyp() + " but expected " + sig.getReceiverType()); } } if (getArguments().size() > sig.getMaxNumParams()) { diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/validation/WurstValidator.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/validation/WurstValidator.java index bf935301a..c1be13ae9 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/validation/WurstValidator.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/validation/WurstValidator.java @@ -369,6 +369,8 @@ private void walkTree(Element root) { private void check(Element e) { try { + if (e instanceof ExprTypeRef) + checkExprTypeRef((ExprTypeRef) e); if (e instanceof Annotation) checkAnnotation((Annotation) e); if (e instanceof AstElementWithTypeParameters) @@ -1676,6 +1678,12 @@ private static boolean isSubtypeCached(WurstType actual, WurstType expected, Ele return actual.isSubtypeOf(expected, site); } + private void checkExprTypeRef(ExprTypeRef e) { + if (!isUsedAsReceiverInExprMember(e)) { + e.addError("Type reference cannot be used as an expression."); + } + } + private void checkAnnotation(Annotation a) { FuncLink fl = a.attrFuncLink(); if (fl == null) return; @@ -1789,12 +1797,37 @@ private void checkParams(Element where, String preMsg, List args, List argTypes; + + if (baseSig.getReceiverType() != null) { + // receiver is lhs, args contains rhs + callSig = new CallSignature(expr.getLeft(), /*hint*/ null, Collections.singletonList(expr.getRight())); + argTypes = Collections.singletonList(expr.getRight().attrTyp()); + } else { + // no receiver, args contains lhs+rhs + callSig = new CallSignature((Expr) null, /*hint*/ null, Lists.newArrayList(expr.getLeft(), expr.getRight())); + argTypes = Lists.newArrayList(expr.getLeft().attrTyp(), expr.getRight().attrTyp()); + } + + // IMPORTANT: specialize the signature using the operand types + FunctionSignature sig = baseSig.matchAgainstArgs(argTypes, expr); + if (sig == null) { + // keep baseSig so we still report something sensible + sig = baseSig; + } + + callSig.checkSignatureCompatibility(sig, "" + expr.getOp(), expr); } private void visit(ExprMemberMethod stmtCall) { diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/GenericsWithTypeclassesTests.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/GenericsWithTypeclassesTests.java index e56c8a4f9..73a234224 100644 --- a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/GenericsWithTypeclassesTests.java +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/GenericsWithTypeclassesTests.java @@ -2020,15 +2020,186 @@ public void genericClassWithLLModule1() { public void genericClassStaticAttribute() { testAssertOkLines(true, "package test", + "native testSuccess()", + "class BoxInt", + " static int count = 1", "class Box", " static int count = 1", "init", - " if Box.count == 1 and Box.count == 1", + " Box.count = 2", + " Box.count = 3", + " BoxInt.count = 4", + " if BoxInt.count == 4 and Box.count == 2 and Box.count == 3", " testSuccess()", "endpackage" ); } + @Test + public void genericClassWithStaticMemberArray() { + testAssertOkLines(true, + "package test", + " native testSuccess()", + " class A", + " static int array foo", + " function setFoo(int v)", + " foo[1] = v", + " function getFoo() returns int", + " return foo[1]", + " init", + " let a = new A", + " let b = new A", + " a.setFoo(3)", + " b.setFoo(1)", + " if a.getFoo() == 3 and b.getFoo() == 1", + " testSuccess()", + "endpackage" + ); + } + + @Test + public void genericClassWithStaticMember() { + testAssertOkLines(true, + "package test", + " native testSuccess()", + " class A", + " static T bar", + " static int foo = 1", + " function setFoo(int v)", + " foo = v", + " function getFoo() returns int", + " return foo", + " init", + " let a = new A", + " let b = new A", + " a.setFoo(3)", + " if a.getFoo() == 3 and b.getFoo() == 1", + " testSuccess()", + "endpackage" + ); + } + + @Test + public void genericClassWithLLModule2() { + testAssertOkLinesWithStdLib(true, + "package test", + "import LinkedListModule", + "", + "class Box", + " use LinkedListModule", + " private T value", + " function setValue(T v)", + " value = v", + " function getValue() returns T", + " return value", + "", + "class OtherBox", + " use LinkedListModule", + " private U v", + " construct(U x)", + " v = x", + " function get() returns U", + " return v", + "init", + " foo()", + "function foo()", + " // Basic sanity", + " let b0 = new Box", + " b0.setValue(42)", + " if b0.getValue() != 42 or b0.prev != null", + " return", + " b0.remove()", + "", + " // Build a 3-element list", + " let b1 = new Box", + " let b2 = new Box", + " let b3 = new Box", + " b1.setValue(1)", + " b2.setValue(2)", + " b3.setValue(3)", + "", + " if Box.getFirst() != b1", + " return", + "", + " // Circular next()", + " if b1.getNext() != b2 or b2.getNext() != b3 or b3.getNext() != b1", + " return", + "", + " // Circular prev()", + " if b1.getPrev() != b3 or b2.getPrev() != b1 or b3.getPrev() != b2", + " return", + "", + " // Forward iteration sum", + " var sum = 0", + " let it1 = Box.iterator()", + " while it1.hasNext()", + " sum += it1.next().getValue()", + " it1.close()", + " if sum != 6", + " return", + "", + " // Backward iteration: sequence 3,2,1 → 321", + " var seq = 0", + " let bit = Box.backIterator()", + " while bit.hasNext()", + " seq = seq * 10 + bit.next().getValue()", + " bit.close()", + " if seq != 321", + " return", + "", + " // Removal in the middle", + " b2.remove()", + " if b1.getNext() != b3 or b3.getPrev() != b1", + " return", + "", + " // Destroy remaining list", + " destroy b1", + " destroy b3", + " let it2 = Box.iterator()", + " if it2.hasNext()", + " return", + " it2.close()", + "", + " // NEW: Generic OtherBox list", + " let o1 = new OtherBox(10)", + " let o2 = new OtherBox(20)", + " let o3 = new OtherBox(30)", + "", + " // Check ordering", + " if OtherBox.getFirst() != o1", + " return", + "", + " if o1.getPrev() != o3 or o3.getNext() != o1", + " return", + "", + " // Sum using static iterator", + " var sumO = 0", + " let oit = OtherBox.staticItr()", + " while oit.hasNext()", + " sumO += oit.next().get()", + " oit.close()", + " if sumO != 60", + " return", + "", + " // Removal test in OtherBox list", + " o2.remove()", + " if o1.getNext() != o3 or o3.getPrev() != o1", + " return", + "", + " destroy o1", + " destroy o3", + "", + " // After destruction list must be empty", + " let oit2 = OtherBox.iterator()", + " if oit2.hasNext()", + " return", + " oit2.close()", + "", + " testSuccess()", + "endpackage" + ); + } + } diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/WurstScriptTest.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/WurstScriptTest.java index 9fa4cf11b..c04a02381 100644 --- a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/WurstScriptTest.java +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/WurstScriptTest.java @@ -399,6 +399,7 @@ protected WurstModel testScript(String name, boolean executeProg, String prog) { private void testWithInliningAndOptimizations(String name, boolean executeProg, boolean executeTests, WurstGui gui, WurstCompilerJassImpl compiler, WurstModel model, boolean executeProgOnlyAfterTransforms, RunArgs runArgs) throws Error { // test with inlining and local optimization + currentTestEnv = "With Inlining and Optimizations"; compiler.setRunArgs(runArgs.with("-inline", "-localOptimizations")); translateAndTest(name + "_inlopt", executeProg, executeTests, gui, compiler, model, executeProgOnlyAfterTransforms); } @@ -406,6 +407,7 @@ private void testWithInliningAndOptimizations(String name, boolean executeProg, private void testWithInliningAndOptimizationsAndStacktraces(String name, boolean executeProg, boolean executeTests, WurstGui gui, WurstCompilerJassImpl compiler, WurstModel model, boolean executeProgOnlyAfterTransforms, RunArgs runArgs) throws Error { // test with inlining and local optimization + currentTestEnv = "With Inlining, Optimizations and Stacktraces"; compiler.setRunArgs(runArgs.with("-inline", "-localOptimizations", "-stacktraces")); translateAndTest(name + "_stacktraceinlopt", executeProg, executeTests, gui, compiler, model, executeProgOnlyAfterTransforms); } @@ -414,6 +416,7 @@ private void testWithInlining(String name, boolean executeProg, boolean executeT , WurstCompilerJassImpl compiler, WurstModel model, boolean executeProgOnlyAfterTransforms , RunArgs runArgs) throws Error { // test with inlining + currentTestEnv = "With Inlining"; compiler.setRunArgs(runArgs.with("-inline")); translateAndTest(name + "_inl", executeProg, executeTests, gui, compiler, model, executeProgOnlyAfterTransforms); } @@ -421,6 +424,7 @@ private void testWithInlining(String name, boolean executeProg, boolean executeT private void testWithLocalOptimizations(String name, boolean executeProg, boolean executeTests, WurstGui gui, WurstCompilerJassImpl compiler, WurstModel model, boolean executeProgOnlyAfterTransforms, RunArgs runArgs) throws Error { // test with local optimization + currentTestEnv = "With Local Optimizations"; compiler.setRunArgs(runArgs.with("-localOptimizations")); translateAndTest(name + "_opt", executeProg, executeTests, gui, compiler, model, executeProgOnlyAfterTransforms); } @@ -430,7 +434,8 @@ private void testWithoutInliningAndOptimization(String name, boolean executeProg throws Error { compiler.setRunArgs(runArgs); // test without inlining and optimization - translateAndTest(name, executeProg, executeTests, gui, compiler, model, executeProgOnlyAfterTransforms); + currentTestEnv = "No opts"; + translateAndTest(name + "_no_opts", executeProg, executeTests, gui, compiler, model, executeProgOnlyAfterTransforms); } private void translateAndTestLua(String name, boolean executeProg, WurstGui gui, WurstModel model, WurstCompilerJassImpl compiler) { @@ -489,7 +494,7 @@ private void translateAndTestLua(String name, boolean executeProg, WurstGui gui, } } if (!success) { - throw new Error("Succeed function not called"); + throw new Error(currentTestEnv + ": Succeed function not called"); } } @@ -520,7 +525,10 @@ private void translateAndTest(String name, boolean executeProg, } if (executeProg) { WLogger.info("Executing imProg before jass transformation"); + String currentEnv = currentTestEnv; + currentTestEnv = "ImProg before jass transformation"; executeImProg(gui, imProg); + currentTestEnv = currentEnv; } } @@ -536,7 +544,10 @@ private void translateAndTest(String name, boolean executeProg, } if (executeProg) { WLogger.info("Executing imProg after jass transformation"); + String currentEnv = currentTestEnv; + currentTestEnv += "-ImProg"; executeImProg(gui, imProg); + currentTestEnv = currentEnv; } @@ -551,7 +562,10 @@ private void translateAndTest(String name, boolean executeProg, runPjass(outputFile); if (executeProg) { + String currentEnv = currentTestEnv; + currentTestEnv += "-JassProg"; executeJassProg(prog); + currentTestEnv = currentEnv; } } @@ -599,6 +613,8 @@ private void runPjass(File outputFile) throws Error { } } + public static String currentTestEnv = ""; + private void executeImProg(WurstGui gui, ImProg imProg) throws TestFailException { try { // run the interpreter on the intermediate language @@ -606,10 +622,10 @@ private void executeImProg(WurstGui gui, ImProg imProg) throws TestFailException interpreter.addNativeProvider(new ReflectionNativeProvider(interpreter)); interpreter.executeFunction("main", null); } catch (TestSuccessException e) { - System.out.println("Suceed function called!"); + System.out.println(currentTestEnv + ": Suceed function called!"); return; } - throw new Error("Succeed function not called"); + throw new Error(currentTestEnv + ": Succeed function not called"); } private void executeJassProg(JassProg prog) @@ -623,7 +639,7 @@ private void executeJassProg(JassProg prog) } catch (TestSuccessException e) { return; } - throw new Error("Succeed function not called"); + throw new Error(currentTestEnv + ": Succeed function not called"); } private void executeTests(WurstGui gui, ImTranslator translator, ImProg imProg) { From 08c9cf8154214864796dc1031348445df1836b87 Mon Sep 17 00:00:00 2001 From: Frotty Date: Fri, 12 Dec 2025 18:05:43 +0100 Subject: [PATCH 4/5] Update WurstParser.java --- .../src/main/java/de/peeeq/wurstscript/WurstParser.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/WurstParser.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/WurstParser.java index 13c9f5369..4d665dd73 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/WurstParser.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/WurstParser.java @@ -112,9 +112,7 @@ public void syntaxError(@SuppressWarnings("null") Recognizer recognizer, @ parser.removeErrorListeners(); parser.addErrorListener(l); - parser.setBuildParseTree(true); CompilationUnitContext cu = parser.compilationUnit(); // begin parsing at init rule - System.out.println(cu.toStringTree(parser)); if (lexer.getTabWarning() != null) { CompileError warning = lexer.getTabWarning(); From b38a607e2e897d95958a74c2b7f04e815cecd969 Mon Sep 17 00:00:00 2001 From: Frotty Date: Fri, 12 Dec 2025 22:40:38 +0100 Subject: [PATCH 5/5] smaller test --- .../attributes/names/NameResolution.java | 37 ++++++++++++++----- .../tests/GenericsWithTypeclassesTests.java | 23 ++++++++++++ 2 files changed, 50 insertions(+), 10 deletions(-) diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/names/NameResolution.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/names/NameResolution.java index 5723b1fec..e23de815e 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/names/NameResolution.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/names/NameResolution.java @@ -117,7 +117,7 @@ private static ImmutableCollection removeDuplicates(List public static ImmutableCollection lookupMemberFuncs(Element node, WurstType receiverType, String name, boolean showErrors) { if (!showErrors) { - GlobalCaches.CacheKey key = new GlobalCaches.CacheKey(node, name + "@" + receiverType, GlobalCaches.LookupType.MEMBER_FUNC); + GlobalCaches.CacheKey key = new GlobalCaches.CacheKey(node, name + "@" + receiverType.getFullName(), GlobalCaches.LookupType.MEMBER_FUNC); @SuppressWarnings("unchecked") ImmutableCollection cached = (ImmutableCollection) GlobalCaches.lookupCache.get(key); if (cached != null) { @@ -155,7 +155,7 @@ public static ImmutableCollection lookupMemberFuncs(Element node, Wurs ImmutableCollection immutableResult = removeDuplicates(result); if (!showErrors) { - GlobalCaches.CacheKey key = new GlobalCaches.CacheKey(node, name + "@" + receiverType, GlobalCaches.LookupType.MEMBER_FUNC); + GlobalCaches.CacheKey key = new GlobalCaches.CacheKey(node, name + "@" + receiverType.getFullName(), GlobalCaches.LookupType.MEMBER_FUNC); GlobalCaches.lookupCache.put(key, immutableResult); } @@ -252,7 +252,7 @@ public static NameLink lookupVarNoConfig(Element node, String name, boolean show public static NameLink lookupMemberVar(Element node, WurstType receiverType, String name, boolean showErrors) { if (!showErrors) { - GlobalCaches.CacheKey key = new GlobalCaches.CacheKey(node, name + "@" + receiverType, GlobalCaches.LookupType.MEMBER_VAR); + GlobalCaches.CacheKey key = new GlobalCaches.CacheKey(node, name + "@" + receiverType.getFullName(), GlobalCaches.LookupType.MEMBER_VAR); NameLink cached = (NameLink) GlobalCaches.lookupCache.get(key); if (cached != null) { return cached; @@ -286,7 +286,7 @@ public static NameLink lookupMemberVar(Element node, WurstType receiverType, Str if (bestMatch != null) { if (!showErrors) { - GlobalCaches.CacheKey key = new GlobalCaches.CacheKey(node, name + "@" + receiverType, GlobalCaches.LookupType.MEMBER_VAR); + GlobalCaches.CacheKey key = new GlobalCaches.CacheKey(node, name + "@" + receiverType.getFullName(), GlobalCaches.LookupType.MEMBER_VAR); GlobalCaches.lookupCache.put(key, bestMatch.link); } return bestMatch.link; @@ -413,13 +413,30 @@ private DefLinkMatch(DefLink link, int distance) { public static DefLink matchDefLinkReceiver(DefLink n, WurstType receiverType, Element node, boolean showErrors) { WurstType n_receiverType = n.getReceiverType(); - if (n_receiverType == null) { - return null; - } - VariableBinding mapping = receiverType.matchAgainstSupertype(n_receiverType, node, VariableBinding.emptyMapping().withTypeVariables(n.getTypeParams()), VariablePosition.RIGHT); - if (mapping == null) { - return null; + if (n_receiverType == null) return null; + + VariableBinding vb = VariableBinding.emptyMapping(); + + // 1) include type params from the receiver type (class/interface/module instantiation) + if (n_receiverType instanceof WurstTypeClassOrInterface wtc) { + if (wtc.getDef() instanceof AstElementWithTypeParameters tpOwner) { + vb = vb.withTypeVariables(tpOwner.getTypeParameters()); + } + } else if (n_receiverType instanceof WurstTypeModuleInstanciation mins) { + ClassDef cd = mins.getDef().attrNearestClassDef(); + if (cd != null) { + vb = vb.withTypeVariables(cd.getTypeParameters()); + } } + + // 2) include function/extension type params as before + vb = vb.withTypeVariables(n.getTypeParams()); + + VariableBinding mapping = + receiverType.matchAgainstSupertype(n_receiverType, node, vb, VariablePosition.RIGHT); + + if (mapping == null) return null; + if (showErrors) { if (n.getVisibility() == Visibility.PRIVATE_OTHER) { node.addError(Utils.printElement(n.getDef()) + " is private and cannot be used here."); diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/GenericsWithTypeclassesTests.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/GenericsWithTypeclassesTests.java index 73a234224..6c01be619 100644 --- a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/GenericsWithTypeclassesTests.java +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/GenericsWithTypeclassesTests.java @@ -2200,6 +2200,29 @@ public void genericClassWithLLModule2() { ); } + @Test + public void genericModuleThistypeSmall() { + testAssertOkLines(true, + "package test", + " native testSuccess()", + " module LLM", + " static thistype t", + " construct()", + " t = this", + " function iterator() returns Iterator", + " return new Iterator()", + " static class Iterator", + " function next() returns LLM.thistype", + " return t", + " class A", + " use LLM", + " init", + " let a = new A", + " if a.iterator().next() == a", + " testSuccess()", + "endpackage" + ); + } }