From 1ec8b589194be846fc6d5c45ebb9b5c3b398c19a Mon Sep 17 00:00:00 2001 From: Frotty Date: Thu, 28 May 2026 23:29:22 +0200 Subject: [PATCH 1/5] native access for wrappers --- Wurstpack/wurstscript/grill | 2 +- Wurstpack/wurstscript/grill.cmd | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Wurstpack/wurstscript/grill b/Wurstpack/wurstscript/grill index eaa907174..ebcdebfd9 100644 --- a/Wurstpack/wurstscript/grill +++ b/Wurstpack/wurstscript/grill @@ -23,4 +23,4 @@ if [[ ! -x "$RUNTIME" ]]; then exit 3 fi -exec "$RUNTIME" -Dfile.encoding=UTF-8 -jar "$JAR" "$@" +exec "$RUNTIME" -Dfile.encoding=UTF-8 --enable-native-access=ALL-UNNAMED -jar "$JAR" "$@" diff --git a/Wurstpack/wurstscript/grill.cmd b/Wurstpack/wurstscript/grill.cmd index d39767b11..3e6f46c05 100644 --- a/Wurstpack/wurstscript/grill.cmd +++ b/Wurstpack/wurstscript/grill.cmd @@ -31,7 +31,7 @@ if not exist "%JAVA%" ( goto :restore ) -"%JAVA%" -Dfile.encoding=UTF-8 -jar "%GRILL_JAR%" %* +"%JAVA%" -Dfile.encoding=UTF-8 --enable-native-access=ALL-UNNAMED -jar "%GRILL_JAR%" %* :restore rem Restore previous code page if we captured it From ad92658a3223ed92df3acef83a6e73dfa336f218 Mon Sep 17 00:00:00 2001 From: Frotty Date: Thu, 28 May 2026 23:35:09 +0200 Subject: [PATCH 2/5] simply override objmods if redefined in code --- .../interpreter/CompiletimeNatives.java | 6 +- .../interpreter/ProgramStateIO.java | 6 ++ .../tests/CompiletimeNativesTest.java | 81 +++++++++++++++++++ 3 files changed, 91 insertions(+), 2 deletions(-) diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/intermediateLang/interpreter/CompiletimeNatives.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/intermediateLang/interpreter/CompiletimeNatives.java index c52418ec8..76054b935 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/intermediateLang/interpreter/CompiletimeNatives.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/intermediateLang/interpreter/CompiletimeNatives.java @@ -41,8 +41,8 @@ public ILconstTuple createObjectDefinition(ILconstString fileType, ILconstInt ne String objIdString = ObjectHelper.objectIdIntToString(newUnitId.getVal()); boolean isMeleeOverride = newUnitId.getVal() == deriveFrom.getVal(); - if (!isMeleeOverride && dataStore.getObjs().containsKey(ObjId.valueOf(objIdString))) { - globalState.compilationError("Object definition with id " + objIdString + " already exists."); + if (!globalState.registerCreatedObjectDefinition(fileType.getVal(), objIdString)) { + globalState.compilationError("Object definition with id " + objIdString + " is defined more than once."); } ObjMod.Obj objDef = newDefFromFiletype(dataStore, deriveFrom.getVal(), newUnitId.getVal(), isMeleeOverride); if (!isMeleeOverride) { @@ -59,10 +59,12 @@ private ObjMod.Obj newDefFromFiletype(ObjMod dataStore, in if (isMeleeOverride) { ObjId id = ObjId.valueOf(ObjectHelper.objectIdIntToString(newId)); // same id => modify melee/original definition table + dataStore.removeObj(id); return dataStore.addObj(id, null); } ObjId baseIdS = ObjId.valueOf(ObjectHelper.objectIdIntToString(base)); ObjId newIdS = ObjId.valueOf(ObjectHelper.objectIdIntToString(newId)); + dataStore.removeObj(newIdS); return dataStore.addObj(newIdS, baseIdS); } diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/intermediateLang/interpreter/ProgramStateIO.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/intermediateLang/interpreter/ProgramStateIO.java index 4441149f9..0bae32ac2 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/intermediateLang/interpreter/ProgramStateIO.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/intermediateLang/interpreter/ProgramStateIO.java @@ -36,6 +36,7 @@ public class ProgramStateIO extends ProgramState { private @Nullable final MpqEditor mpqEditor; private final Map> dataStoreMap = Maps.newLinkedHashMap(); private final Map dataStoreHashes = Maps.newLinkedHashMap(); + private final Map> createdObjectDefinitionIds = Maps.newLinkedHashMap(); private int id = 0; private final Map objDefinitions = Maps.newLinkedHashMap(); private PrintStream outStream = System.err; @@ -368,6 +369,11 @@ ObjMod.Obj getObjectDefinition(String key) { return objDefinitions.get(key); } + boolean registerCreatedObjectDefinition(String fileExtension, String objId) { + Set ids = createdObjectDefinitionIds.computeIfAbsent(fileExtension, k -> new LinkedHashSet<>()); + return ids.add(objId); + } + /** * Calculate hash of an object file's contents */ diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/CompiletimeNativesTest.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/CompiletimeNativesTest.java index 653b2fc45..76ffd7a37 100644 --- a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/CompiletimeNativesTest.java +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/CompiletimeNativesTest.java @@ -1,23 +1,34 @@ package tests.wurstscript.tests; import de.peeeq.wurstio.intermediateLang.interpreter.CompiletimeNatives; +import de.peeeq.wurstio.intermediateLang.interpreter.ProgramStateIO; import de.peeeq.wurstio.objectreader.ObjectHelper; +import de.peeeq.wurstscript.ast.Ast; +import de.peeeq.wurstscript.ast.Element; +import de.peeeq.wurstscript.gui.WurstGuiLogger; +import de.peeeq.wurstscript.intermediatelang.ILconstInt; import de.peeeq.wurstscript.intermediatelang.ILconstString; +import de.peeeq.wurstscript.jassIm.ImProg; +import de.peeeq.wurstscript.jassIm.JassIm; import net.moonlightflower.wc3libs.bin.ObjMod; import net.moonlightflower.wc3libs.bin.app.objMod.W3A; import net.moonlightflower.wc3libs.bin.app.objMod.W3U; import net.moonlightflower.wc3libs.dataTypes.DataType; +import net.moonlightflower.wc3libs.dataTypes.app.War3String; import net.moonlightflower.wc3libs.dataTypes.app.War3Real; import net.moonlightflower.wc3libs.misc.MetaFieldId; import net.moonlightflower.wc3libs.misc.ObjId; import org.testng.annotations.Test; import java.lang.reflect.Method; +import java.util.HashMap; import java.util.List; +import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; public class CompiletimeNativesTest { @@ -104,4 +115,74 @@ public void differentIdsObjectDefinitionUsesCustomTable() throws Exception { assertEquals(w3u.getOrigObjs().size(), 0); assertEquals(w3u.getCustomObjs().size(), 1); } + + @Test + public void differentIdsObjectDefinitionOverwritesExistingMapObject() throws Exception { + CompiletimeNatives natives = new CompiletimeNatives(null, null, false); + W3U w3u = new W3U(); + int hfoo = ObjectHelper.objectIdStringToInt("hfoo"); + int hf01 = ObjectHelper.objectIdStringToInt("hf01"); + W3U.Obj existing = w3u.addObj(ObjId.valueOf("hf01"), ObjId.valueOf("hpea")); + existing.addMod(new ObjMod.Obj.Mod(MetaFieldId.valueOf("unam"), ObjMod.ValType.STRING, War3String.valueOf("old map object"))); + + Method newDefFromFiletype = CompiletimeNatives.class.getDeclaredMethod( + "newDefFromFiletype", + ObjMod.class, + int.class, + int.class, + boolean.class + ); + newDefFromFiletype.setAccessible(true); + + ObjMod.Obj obj = (ObjMod.Obj) newDefFromFiletype.invoke(natives, w3u, hfoo, hf01, false); + + assertEquals(w3u.getCustomObjs().size(), 1, "Existing map object should be replaced instead of duplicated"); + assertEquals(obj.getId().getVal(), "hf01"); + assertEquals(obj.getBaseId().getVal(), "hfoo"); + assertEquals(obj.getMods().size(), 0, "Overwritten map object mods should not leak into the Wurst-created object"); + } + + @Test + public void duplicateCodeObjectDefinitionsReportError() { + WurstGuiLogger gui = new WurstGuiLogger(); + ProgramStateIO state = new ProgramStateIO(Optional.empty(), null, gui, emptyProg(), true); + CompiletimeNatives natives = new CompiletimeNatives(state, null, false); + int hfoo = ObjectHelper.objectIdStringToInt("hfoo"); + int hf01 = ObjectHelper.objectIdStringToInt("hf01"); + + natives.createObjectDefinition(new ILconstString("w3u"), new ILconstInt(hf01), new ILconstInt(hfoo)); + natives.createObjectDefinition(new ILconstString("w3u"), new ILconstInt(hf01), new ILconstInt(hfoo)); + + assertEquals(gui.getErrorCount(), 1); + assertTrue(gui.getErrors().contains("Object definition with id hf01 is defined more than once.")); + } + + @Test + public void createObjectDefinitionDoesNotReportExistingMapObjectAsError() throws Exception { + WurstGuiLogger gui = new WurstGuiLogger(); + ProgramStateIO state = new ProgramStateIO(Optional.empty(), null, gui, emptyProg(), true); + CompiletimeNatives natives = new CompiletimeNatives(state, null, false); + int hfoo = ObjectHelper.objectIdStringToInt("hfoo"); + int hf01 = ObjectHelper.objectIdStringToInt("hf01"); + + Method getDataStore = ProgramStateIO.class.getDeclaredMethod("getDataStore", String.class); + getDataStore.setAccessible(true); + W3U w3u = (W3U) getDataStore.invoke(state, "w3u"); + W3U.Obj existing = w3u.addObj(ObjId.valueOf("hf01"), ObjId.valueOf("hpea")); + existing.addMod(new ObjMod.Obj.Mod(MetaFieldId.valueOf("unam"), ObjMod.ValType.STRING, War3String.valueOf("old map object"))); + + natives.createObjectDefinition(new ILconstString("w3u"), new ILconstInt(hf01), new ILconstInt(hfoo)); + + assertEquals(gui.getErrorCount(), 0); + assertEquals(w3u.getCustomObjs().size(), 1); + ObjMod.Obj obj = w3u.getCustomObjs().get(0); + assertEquals(obj.getId().getVal(), "hf01"); + assertEquals(obj.getBaseId().getVal(), "hfoo"); + assertFalse(obj.getMods().stream().anyMatch(m -> m.getId().getVal().equals("unam"))); + } + + private ImProg emptyProg() { + Element trace = Ast.NoExpr(); + return JassIm.ImProg(trace, JassIm.ImVars(), JassIm.ImFunctions(), JassIm.ImMethods(), JassIm.ImClasses(), JassIm.ImTypeClassFuncs(), new HashMap<>()); + } } From f8f8a08f481c76ea648b08cd0cf595fc96a2133b Mon Sep 17 00:00:00 2001 From: Frotty Date: Fri, 29 May 2026 00:20:20 +0200 Subject: [PATCH 3/5] and some validation performance --- .../de/peeeq/wurstscript/WurstChecker.java | 10 +++ .../wurstscript/attributes/names/Exports.java | 61 +++++++++++++++++ .../attributes/names/NameLinks.java | 65 +++++++++++++++++++ .../attributes/names/NameResolution.java | 12 ++++ .../attributes/names/TypeNameLinks.java | 27 ++++++++ .../wurstscript/validation/GlobalCaches.java | 26 ++++++++ .../validation/WurstValidator.java | 17 ++--- 7 files changed, 208 insertions(+), 10 deletions(-) diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/WurstChecker.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/WurstChecker.java index f456412c6..a0acd43f2 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/WurstChecker.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/WurstChecker.java @@ -6,6 +6,7 @@ import de.peeeq.wurstscript.attributes.ErrorHandler; import de.peeeq.wurstscript.attributes.names.DesugarArrayLength; import de.peeeq.wurstscript.gui.WurstGui; +import de.peeeq.wurstscript.validation.GlobalCaches; import de.peeeq.wurstscript.validation.TRVEHelper; import de.peeeq.wurstscript.validation.WurstValidator; @@ -34,6 +35,7 @@ public void checkProg(WurstModel root, Collection toCheck) { if (errorHandler.getErrorCount() > 0) return; attachErrorHandler(root); + clearGlobalCaches(root, toCheck); expandModules(root); @@ -50,6 +52,14 @@ public void checkProg(WurstModel root, Collection toCheck) { validator.validate(toCheck); } + private void clearGlobalCaches(WurstModel root, Collection toCheck) { + if (toCheck == root || toCheck.size() >= root.size()) { + GlobalCaches.clearAll(); + } else { + GlobalCaches.clearLookupCacheFor(toCheck); + } + } + private void attachErrorHandler(WurstModel root) { for (CompilationUnit cu : root) { cu.getCuInfo().setCuErrorHandler(errorHandler); diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/names/Exports.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/names/Exports.java index 9318ec3d6..3e55469c9 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/names/Exports.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/names/Exports.java @@ -1,8 +1,10 @@ package de.peeeq.wurstscript.attributes.names; +import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.ImmutableMultimap.Builder; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import de.peeeq.wurstscript.ast.WImport; import de.peeeq.wurstscript.ast.WPackage; @@ -47,12 +49,48 @@ private static void addExportedNameLinks(Builder result, WPacka } + public static ImmutableCollection exportedNameLinks(WPackage p, String name) { + ImmutableSet.Builder result = ImmutableSet.builder(); + addExportedNameLinks(result, p, name, Sets.newLinkedHashSet()); + return result.build(); + } + + private static void addExportedNameLinks(ImmutableSet.Builder result, WPackage p, String name, Set alreadyImported) { + if (alreadyImported.contains(p)) { + return; + } + alreadyImported.add(p); + + for (DefLink link : NameLinks.get(p.getElements(), name)) { + if (link.getVisibility() == Visibility.LOCAL) { + continue; + } + result.add(link.hidingPrivateAndProtected()); + } + + for (WImport imp2 : p.getImports()) { + if (imp2.getIsPublic()) { + @Nullable + WPackage imported = imp2.attrImportedPackage(); + if (imported != null) { + addExportedNameLinks(result, imported, name, alreadyImported); + } + } + } + } + public static ImmutableMultimap exportedTypeNameLinks(WPackage p) { Builder result = ImmutableMultimap.builder(); addExportedTypeNameLinks(result, p, Sets.newLinkedHashSet()); return result.build(); } + public static ImmutableCollection exportedTypeNameLinks(WPackage p, String name) { + ImmutableSet.Builder result = ImmutableSet.builder(); + addExportedTypeNameLinks(result, p, name, Sets.newLinkedHashSet()); + return result.build(); + } + private static void addExportedTypeNameLinks(Builder result, WPackage p, Set alreadyImported) { if (alreadyImported.contains(p)) { @@ -73,4 +111,27 @@ private static void addExportedTypeNameLinks(Builder result, W } } + + private static void addExportedTypeNameLinks(ImmutableSet.Builder result, WPackage p, String name, Set alreadyImported) { + if (alreadyImported.contains(p)) { + return; + } + alreadyImported.add(p); + + for (TypeLink link : TypeNameLinks.get(p.getElements(), name)) { + if (link.getVisibility() == Visibility.LOCAL) { + continue; + } + result.add(link.hidingPrivateAndProtected()); + } + + for (WImport imp2 : p.getImports()) { + if (imp2.getIsPublic()) { + WPackage imported = imp2.attrImportedPackage(); + if (imported != null) { + addExportedTypeNameLinks(result, imported, name, alreadyImported); + } + } + } + } } diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/names/NameLinks.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/names/NameLinks.java index ac8782e8e..6d885e9fb 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/names/NameLinks.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/names/NameLinks.java @@ -1,8 +1,11 @@ package de.peeeq.wurstscript.attributes.names; import com.google.common.collect.HashMultimap; +import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.ImmutableMultimap.Builder; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSetMultimap; import com.google.common.collect.Multimap; import de.peeeq.wurstscript.WLogger; @@ -268,6 +271,27 @@ public static ImmutableMultimap calculate(WPackage p) { return result.build(); } + public static ImmutableCollection get(WPackage p, String name) { + ImmutableSet.Builder result = ImmutableSet.builder(); + for (WImport imp : p.getImports()) { + if (imp.getPackagename().equals("NoWurst")) { + continue; + } + WPackage importedPackage = imp.attrImportedPackage(); + if (importedPackage == null) { + WLogger.info("could not resolve import: " + Utils.printElementWithSource(Optional.of(imp))); + continue; + } + if (p.getName().equals("WurstREPL")) { + addHidingPrivate(result, importedPackage.getElements().attrNameLinks().get(name)); + result.addAll(get(importedPackage, name)); + } else { + result.addAll(Exports.exportedNameLinks(importedPackage, name)); + } + } + return result.build(); + } + public static ImmutableMultimap calculate(WEntities wEntities) { ImmutableMultimap.Builder result = ImmutableSetMultimap.builder(); @@ -290,6 +314,29 @@ public static ImmutableMultimap calculate(WEntities wEntities) return result.build(); } + public static ImmutableCollection get(WEntities wEntities, String name) { + ImmutableSet.Builder result = ImmutableSet.builder(); + for (WEntity e : wEntities) { + if (e instanceof NameDef) { + NameDef n = (NameDef) e; + if (n.getName().equals(name)) { + addNameDefDefLink(result::add, n, wEntities); + } + } + if (e instanceof WScope && !(e instanceof ModuleDef)) { + WScope scope = (WScope) e; + List typeParams; + if (scope instanceof AstElementWithTypeParameters) { + typeParams = ((AstElementWithTypeParameters) scope).getTypeParameters(); + } else { + typeParams = Collections.emptyList(); + } + addHidingPrivate(result, scope.attrNameLinks().get(name), typeParams); + } + } + return result.build(); + } + public static ImmutableMultimap calculate(WurstModel model) { ImmutableMultimap.Builder result = ImmutableSetMultimap.builder(); for (CompilationUnit cu : model) { @@ -398,6 +445,24 @@ public static void addHidingPrivate(Builder result, Multimap result, Iterable adding) { + for (DefLink defLink : adding) { + if (defLink.getVisibility() == Visibility.LOCAL) { + continue; + } + result.add(defLink.hidingPrivate()); + } + } + + private static void addHidingPrivate(ImmutableSet.Builder result, Iterable adding, List typeParams) { + for (DefLink defLink : adding) { + if (defLink.getVisibility() == Visibility.LOCAL) { + continue; + } + result.add(defLink.hidingPrivate().withGenericTypeParams(typeParams)); + } + } + public static void addHidingPrivate(Multimap result, Multimap adding) { for (Entry e : adding.entries()) { if (e.getValue().getVisibility() == Visibility.LOCAL) { 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 810723097..551d463bf 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 @@ -21,10 +21,22 @@ private static String memberFuncCacheName(String name, WurstType receiverType) { } private static ImmutableCollection scopeNameLinks(WScope scope, String name) { + if (scope instanceof WPackage) { + return NameLinks.get((WPackage) scope, name); + } + if (scope instanceof WEntities) { + return NameLinks.get((WEntities) scope, name); + } return scope.attrNameLinks().get(name); } private static ImmutableCollection scopeTypeLinks(WScope scope, String name) { + if (scope instanceof WPackage) { + return TypeNameLinks.get((WPackage) scope, name); + } + if (scope instanceof WEntities) { + return TypeNameLinks.get((WEntities) scope, name); + } return scope.attrTypeNameLinks().get(name); } diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/names/TypeNameLinks.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/names/TypeNameLinks.java index f734ff836..bcd907d93 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/names/TypeNameLinks.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/names/TypeNameLinks.java @@ -1,6 +1,8 @@ package de.peeeq.wurstscript.attributes.names; +import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSetMultimap; import de.peeeq.wurstscript.ast.*; @@ -65,6 +67,18 @@ public static ImmutableMultimap calculate(WPackage p) { return result.build(); } + public static ImmutableCollection get(WPackage p, String name) { + ImmutableSet.Builder result = ImmutableSet.builder(); + for (WImport imp : p.getImports()) { + WPackage importedPackage = imp.attrImportedPackage(); + if (importedPackage == null) { + continue; + } + result.addAll(Exports.exportedTypeNameLinks(importedPackage, name)); + } + return result.build(); + } + public static ImmutableMultimap calculate(WEntities wEntities) { ImmutableMultimap.Builder result = ImmutableSetMultimap.builder(); for (WEntity e : wEntities) { @@ -76,6 +90,19 @@ public static ImmutableMultimap calculate(WEntities wEntities) return result.build(); } + public static ImmutableCollection get(WEntities wEntities, String name) { + ImmutableSet.Builder result = ImmutableSet.builder(); + for (WEntity e : wEntities) { + if (e instanceof TypeDef) { + TypeDef n = (TypeDef) e; + if (n.getName().equals(name)) { + result.add(TypeLink.create(n, wEntities)); + } + } + } + return result.build(); + } + public static ImmutableMultimap calculate(WurstModel model) { ImmutableMultimap.Builder result = ImmutableSetMultimap.builder(); for (CompilationUnit cu : model) { diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/validation/GlobalCaches.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/validation/GlobalCaches.java index bb6cce60e..ac8380a50 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/validation/GlobalCaches.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/validation/GlobalCaches.java @@ -6,8 +6,12 @@ import de.peeeq.wurstscript.intermediatelang.interpreter.LocalState; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.ArrayDeque; import java.util.Arrays; +import java.util.Collection; +import java.util.IdentityHashMap; import java.util.Map; +import java.util.Set; import java.util.concurrent.atomic.AtomicLong; // Expose static fields only if you already have them there; otherwise, just clear via dedicated methods. @@ -146,6 +150,28 @@ public static void clearAll() { HasAnnotation.clearCaches(); } + /** + * Clears lookup entries whose query element belongs to one of the changed roots. + * This keeps editor validation from throwing away hot lookup data for unchanged files. + */ + public static void clearLookupCacheFor(Collection roots) { + if (roots.isEmpty()) { + return; + } + Set affected = java.util.Collections.newSetFromMap(new IdentityHashMap<>()); + ArrayDeque todo = new ArrayDeque<>(roots); + while (!todo.isEmpty()) { + Element e = todo.removeLast(); + if (!affected.add(e)) { + continue; + } + for (int i = 0; i < e.size(); i++) { + todo.add(e.get(i)); + } + } + lookupCache.keySet().removeIf(key -> affected.contains(key.element)); + } + public enum LookupType { FUNC, VAR, TYPE, PACKAGE, MEMBER_FUNC, MEMBER_VAR } 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 7bb45a4f1..d8df0a417 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 @@ -64,11 +64,10 @@ public WurstValidator(WurstModel root) { public void validate(Collection toCheck) { try { - functionCount = countFunctions(); + functionCount = countFunctions(toCheck); visitedFunctions = 0; heavyFunctions.clear(); heavyBlocks.clear(); - GlobalCaches.clearAll(); lightValidation(toCheck); @@ -123,11 +122,6 @@ private void lightValidation(Collection toCheck) { for (CompilationUnit cu : toCheck) { walkTree(cu); } - - // Build CFG once for heavy phase (enables reachability/prev/next attrs) - for (CompilationUnit cu : toCheck) { - computeFlowAttributes(cu); - } } /** Visit only statements under root and run checkReachability where applicable. */ @@ -1174,16 +1168,19 @@ private void checkIntVal(ExprIntVal e) { // check range? ... } - private int countFunctions() { + private int countFunctions(Collection toCheck) { final int[] functionCount = new int[1]; - prog.accept(new WurstModel.DefaultVisitor() { + Element.DefaultVisitor visitor = new Element.DefaultVisitor() { @Override public void visit(FuncDef f) { super.visit(f); functionCount[0]++; } - }); + }; + for (CompilationUnit cu : toCheck) { + cu.accept(visitor); + } return functionCount[0]; } From d79658f35c5a2f12f0c1f41698b9f04529227902 Mon Sep 17 00:00:00 2001 From: Frotty Date: Fri, 29 May 2026 01:04:32 +0200 Subject: [PATCH 4/5] fixes --- .../interpreter/CompiletimeNatives.java | 7 +- .../wurstscript/attributes/names/Exports.java | 62 ----------------- .../attributes/names/NameLinks.java | 66 ------------------- .../attributes/names/NameResolution.java | 12 ---- .../attributes/names/TypeNameLinks.java | 27 -------- .../tests/CompiletimeNativesTest.java | 23 +++++++ 6 files changed, 28 insertions(+), 169 deletions(-) diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/intermediateLang/interpreter/CompiletimeNatives.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/intermediateLang/interpreter/CompiletimeNatives.java index 76054b935..96aa66dec 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/intermediateLang/interpreter/CompiletimeNatives.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/intermediateLang/interpreter/CompiletimeNatives.java @@ -41,7 +41,7 @@ public ILconstTuple createObjectDefinition(ILconstString fileType, ILconstInt ne String objIdString = ObjectHelper.objectIdIntToString(newUnitId.getVal()); boolean isMeleeOverride = newUnitId.getVal() == deriveFrom.getVal(); - if (!globalState.registerCreatedObjectDefinition(fileType.getVal(), objIdString)) { + if (!isMeleeOverride && !globalState.registerCreatedObjectDefinition(fileType.getVal(), objIdString)) { globalState.compilationError("Object definition with id " + objIdString + " is defined more than once."); } ObjMod.Obj objDef = newDefFromFiletype(dataStore, deriveFrom.getVal(), newUnitId.getVal(), isMeleeOverride); @@ -59,7 +59,10 @@ private ObjMod.Obj newDefFromFiletype(ObjMod dataStore, in if (isMeleeOverride) { ObjId id = ObjId.valueOf(ObjectHelper.objectIdIntToString(newId)); // same id => modify melee/original definition table - dataStore.removeObj(id); + ObjMod.Obj existing = dataStore.getObjs().get(id); + if (existing != null) { + return existing; + } return dataStore.addObj(id, null); } ObjId baseIdS = ObjId.valueOf(ObjectHelper.objectIdIntToString(base)); diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/names/Exports.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/names/Exports.java index 3e55469c9..4f2a5c36d 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/names/Exports.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/names/Exports.java @@ -1,10 +1,8 @@ package de.peeeq.wurstscript.attributes.names; -import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.ImmutableMultimap.Builder; -import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import de.peeeq.wurstscript.ast.WImport; import de.peeeq.wurstscript.ast.WPackage; @@ -49,49 +47,12 @@ private static void addExportedNameLinks(Builder result, WPacka } - public static ImmutableCollection exportedNameLinks(WPackage p, String name) { - ImmutableSet.Builder result = ImmutableSet.builder(); - addExportedNameLinks(result, p, name, Sets.newLinkedHashSet()); - return result.build(); - } - - private static void addExportedNameLinks(ImmutableSet.Builder result, WPackage p, String name, Set alreadyImported) { - if (alreadyImported.contains(p)) { - return; - } - alreadyImported.add(p); - - for (DefLink link : NameLinks.get(p.getElements(), name)) { - if (link.getVisibility() == Visibility.LOCAL) { - continue; - } - result.add(link.hidingPrivateAndProtected()); - } - - for (WImport imp2 : p.getImports()) { - if (imp2.getIsPublic()) { - @Nullable - WPackage imported = imp2.attrImportedPackage(); - if (imported != null) { - addExportedNameLinks(result, imported, name, alreadyImported); - } - } - } - } - public static ImmutableMultimap exportedTypeNameLinks(WPackage p) { Builder result = ImmutableMultimap.builder(); addExportedTypeNameLinks(result, p, Sets.newLinkedHashSet()); return result.build(); } - public static ImmutableCollection exportedTypeNameLinks(WPackage p, String name) { - ImmutableSet.Builder result = ImmutableSet.builder(); - addExportedTypeNameLinks(result, p, name, Sets.newLinkedHashSet()); - return result.build(); - } - - private static void addExportedTypeNameLinks(Builder result, WPackage p, Set alreadyImported) { if (alreadyImported.contains(p)) { return; @@ -111,27 +72,4 @@ private static void addExportedTypeNameLinks(Builder result, W } } - - private static void addExportedTypeNameLinks(ImmutableSet.Builder result, WPackage p, String name, Set alreadyImported) { - if (alreadyImported.contains(p)) { - return; - } - alreadyImported.add(p); - - for (TypeLink link : TypeNameLinks.get(p.getElements(), name)) { - if (link.getVisibility() == Visibility.LOCAL) { - continue; - } - result.add(link.hidingPrivateAndProtected()); - } - - for (WImport imp2 : p.getImports()) { - if (imp2.getIsPublic()) { - WPackage imported = imp2.attrImportedPackage(); - if (imported != null) { - addExportedTypeNameLinks(result, imported, name, alreadyImported); - } - } - } - } } diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/names/NameLinks.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/names/NameLinks.java index 6d885e9fb..9fd9b602b 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/names/NameLinks.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/names/NameLinks.java @@ -1,11 +1,8 @@ package de.peeeq.wurstscript.attributes.names; import com.google.common.collect.HashMultimap; -import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.ImmutableMultimap.Builder; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSetMultimap; import com.google.common.collect.Multimap; import de.peeeq.wurstscript.WLogger; @@ -271,28 +268,6 @@ public static ImmutableMultimap calculate(WPackage p) { return result.build(); } - public static ImmutableCollection get(WPackage p, String name) { - ImmutableSet.Builder result = ImmutableSet.builder(); - for (WImport imp : p.getImports()) { - if (imp.getPackagename().equals("NoWurst")) { - continue; - } - WPackage importedPackage = imp.attrImportedPackage(); - if (importedPackage == null) { - WLogger.info("could not resolve import: " + Utils.printElementWithSource(Optional.of(imp))); - continue; - } - if (p.getName().equals("WurstREPL")) { - addHidingPrivate(result, importedPackage.getElements().attrNameLinks().get(name)); - result.addAll(get(importedPackage, name)); - } else { - result.addAll(Exports.exportedNameLinks(importedPackage, name)); - } - } - return result.build(); - } - - public static ImmutableMultimap calculate(WEntities wEntities) { ImmutableMultimap.Builder result = ImmutableSetMultimap.builder(); for (WEntity e : wEntities) { @@ -314,29 +289,6 @@ public static ImmutableMultimap calculate(WEntities wEntities) return result.build(); } - public static ImmutableCollection get(WEntities wEntities, String name) { - ImmutableSet.Builder result = ImmutableSet.builder(); - for (WEntity e : wEntities) { - if (e instanceof NameDef) { - NameDef n = (NameDef) e; - if (n.getName().equals(name)) { - addNameDefDefLink(result::add, n, wEntities); - } - } - if (e instanceof WScope && !(e instanceof ModuleDef)) { - WScope scope = (WScope) e; - List typeParams; - if (scope instanceof AstElementWithTypeParameters) { - typeParams = ((AstElementWithTypeParameters) scope).getTypeParameters(); - } else { - typeParams = Collections.emptyList(); - } - addHidingPrivate(result, scope.attrNameLinks().get(name), typeParams); - } - } - return result.build(); - } - public static ImmutableMultimap calculate(WurstModel model) { ImmutableMultimap.Builder result = ImmutableSetMultimap.builder(); for (CompilationUnit cu : model) { @@ -445,24 +397,6 @@ public static void addHidingPrivate(Builder result, Multimap result, Iterable adding) { - for (DefLink defLink : adding) { - if (defLink.getVisibility() == Visibility.LOCAL) { - continue; - } - result.add(defLink.hidingPrivate()); - } - } - - private static void addHidingPrivate(ImmutableSet.Builder result, Iterable adding, List typeParams) { - for (DefLink defLink : adding) { - if (defLink.getVisibility() == Visibility.LOCAL) { - continue; - } - result.add(defLink.hidingPrivate().withGenericTypeParams(typeParams)); - } - } - public static void addHidingPrivate(Multimap result, Multimap adding) { for (Entry e : adding.entries()) { if (e.getValue().getVisibility() == Visibility.LOCAL) { 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 551d463bf..810723097 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 @@ -21,22 +21,10 @@ private static String memberFuncCacheName(String name, WurstType receiverType) { } private static ImmutableCollection scopeNameLinks(WScope scope, String name) { - if (scope instanceof WPackage) { - return NameLinks.get((WPackage) scope, name); - } - if (scope instanceof WEntities) { - return NameLinks.get((WEntities) scope, name); - } return scope.attrNameLinks().get(name); } private static ImmutableCollection scopeTypeLinks(WScope scope, String name) { - if (scope instanceof WPackage) { - return TypeNameLinks.get((WPackage) scope, name); - } - if (scope instanceof WEntities) { - return TypeNameLinks.get((WEntities) scope, name); - } return scope.attrTypeNameLinks().get(name); } diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/names/TypeNameLinks.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/names/TypeNameLinks.java index bcd907d93..f734ff836 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/names/TypeNameLinks.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/names/TypeNameLinks.java @@ -1,8 +1,6 @@ package de.peeeq.wurstscript.attributes.names; -import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableMultimap; -import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSetMultimap; import de.peeeq.wurstscript.ast.*; @@ -67,18 +65,6 @@ public static ImmutableMultimap calculate(WPackage p) { return result.build(); } - public static ImmutableCollection get(WPackage p, String name) { - ImmutableSet.Builder result = ImmutableSet.builder(); - for (WImport imp : p.getImports()) { - WPackage importedPackage = imp.attrImportedPackage(); - if (importedPackage == null) { - continue; - } - result.addAll(Exports.exportedTypeNameLinks(importedPackage, name)); - } - return result.build(); - } - public static ImmutableMultimap calculate(WEntities wEntities) { ImmutableMultimap.Builder result = ImmutableSetMultimap.builder(); for (WEntity e : wEntities) { @@ -90,19 +76,6 @@ public static ImmutableMultimap calculate(WEntities wEntities) return result.build(); } - public static ImmutableCollection get(WEntities wEntities, String name) { - ImmutableSet.Builder result = ImmutableSet.builder(); - for (WEntity e : wEntities) { - if (e instanceof TypeDef) { - TypeDef n = (TypeDef) e; - if (n.getName().equals(name)) { - result.add(TypeLink.create(n, wEntities)); - } - } - } - return result.build(); - } - public static ImmutableMultimap calculate(WurstModel model) { ImmutableMultimap.Builder result = ImmutableSetMultimap.builder(); for (CompilationUnit cu : model) { diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/CompiletimeNativesTest.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/CompiletimeNativesTest.java index 76ffd7a37..c8db3168f 100644 --- a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/CompiletimeNativesTest.java +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/CompiletimeNativesTest.java @@ -181,6 +181,29 @@ public void createObjectDefinitionDoesNotReportExistingMapObjectAsError() throws assertFalse(obj.getMods().stream().anyMatch(m -> m.getId().getVal().equals("unam"))); } + @Test + public void sameIdObjectDefinitionsMergeModsWithoutDuplicateError() throws Exception { + WurstGuiLogger gui = new WurstGuiLogger(); + ProgramStateIO state = new ProgramStateIO(Optional.empty(), null, gui, emptyProg(), true); + CompiletimeNatives natives = new CompiletimeNatives(state, null, false); + int hfoo = ObjectHelper.objectIdStringToInt("hfoo"); + + var first = natives.createObjectDefinition(new ILconstString("w3u"), new ILconstInt(hfoo), new ILconstInt(hfoo)); + natives.ObjectDefinition_setString(first, new ILconstString("unam"), new ILconstString("first")); + var second = natives.createObjectDefinition(new ILconstString("w3u"), new ILconstInt(hfoo), new ILconstInt(hfoo)); + natives.ObjectDefinition_setString(second, new ILconstString("utip"), new ILconstString("second")); + + Method getDataStore = ProgramStateIO.class.getDeclaredMethod("getDataStore", String.class); + getDataStore.setAccessible(true); + W3U w3u = (W3U) getDataStore.invoke(state, "w3u"); + + assertEquals(gui.getErrorCount(), 0); + assertEquals(w3u.getOrigObjs().size(), 1); + ObjMod.Obj obj = w3u.getOrigObjs().get(0); + assertTrue(obj.getMods().stream().anyMatch(m -> m.getId().getVal().equals("unam"))); + assertTrue(obj.getMods().stream().anyMatch(m -> m.getId().getVal().equals("utip"))); + } + private ImProg emptyProg() { Element trace = Ast.NoExpr(); return JassIm.ImProg(trace, JassIm.ImVars(), JassIm.ImFunctions(), JassIm.ImMethods(), JassIm.ImClasses(), JassIm.ImTypeClassFuncs(), new HashMap<>()); From 88d5845fdf685872d18fa413ddb2867cc2e81a3b Mon Sep 17 00:00:00 2001 From: Frotty Date: Sun, 31 May 2026 12:08:05 +0200 Subject: [PATCH 5/5] review fix --- .../languageserver/ModelManagerImpl.java | 2 + .../wurstscript/tests/ModelManagerTests.java | 61 +++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/ModelManagerImpl.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/ModelManagerImpl.java index 73b5de9be..2945229bc 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/ModelManagerImpl.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/ModelManagerImpl.java @@ -96,6 +96,7 @@ public Changes removeCompilationUnit(WFile resource) { toRemove.add(compilationUnit); } } + GlobalCaches.clearLookupCacheFor(toRemove); model2.removeAll(toRemove); } @@ -419,6 +420,7 @@ private void updateModel(CompilationUnit cu, WurstGui gui) { Set oldPackages = providedPackages(c); Set mustUpdate = calculateCUsToUpdate(Collections.singletonList(cu), oldPackages, model2); + GlobalCaches.clearLookupCacheFor(Collections.singletonList(c)); clearCompilationUnits(mustUpdate); // replace old compilationunit with new one: it.set(cu); diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/ModelManagerTests.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/ModelManagerTests.java index b8ec87295..671df8987 100644 --- a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/ModelManagerTests.java +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/ModelManagerTests.java @@ -365,6 +365,67 @@ public void readdingMovedFileWithSameContentUpdatesModel() throws IOException { assertNotNull(manager.getCompilationUnit(fileTest), "file should be present after re-adding same contents"); } + @Test + public void replacingCompilationUnitClearsOldLookupCacheKeys() throws IOException { + GlobalCaches.clearAll(); + File projectFolder = new File("./temp/testProject_replace_clears_lookup_cache/"); + File wurstFolder = new File(projectFolder, "wurst"); + newCleanFolder(wurstFolder); + + WFile fileMain = WFile.create(new File(wurstFolder, "Main.wurst")); + WFile fileWurst = WFile.create(new File(wurstFolder, "Wurst.wurst")); + writeFile(fileMain, string( + "package Main", + "public function foo()" + )); + writeFile(fileWurst, "package Wurst\n"); + + ModelManagerImpl manager = new ModelManagerImpl(projectFolder, new BufferManager()); + manager.buildProject(); + CompilationUnit oldCu = manager.getCompilationUnit(fileMain); + assertNotNull(oldCu); + + GlobalCaches.CacheKey oldKey = new GlobalCaches.CacheKey(oldCu, "foo", GlobalCaches.LookupType.FUNC); + GlobalCaches.lookupCache.put(oldKey, oldCu); + + manager.syncCompilationUnitContent(fileMain, string( + "package Main", + "public function bar()" + )); + + assertEquals(GlobalCaches.lookupCache.containsKey(oldKey), false, + "replacing a CU must drop lookup keys that keep the old AST alive"); + } + + @Test + public void removingCompilationUnitClearsOldLookupCacheKeys() throws IOException { + GlobalCaches.clearAll(); + File projectFolder = new File("./temp/testProject_remove_clears_lookup_cache/"); + File wurstFolder = new File(projectFolder, "wurst"); + newCleanFolder(wurstFolder); + + WFile fileMain = WFile.create(new File(wurstFolder, "Main.wurst")); + WFile fileWurst = WFile.create(new File(wurstFolder, "Wurst.wurst")); + writeFile(fileMain, string( + "package Main", + "public function foo()" + )); + writeFile(fileWurst, "package Wurst\n"); + + ModelManagerImpl manager = new ModelManagerImpl(projectFolder, new BufferManager()); + manager.buildProject(); + CompilationUnit oldCu = manager.getCompilationUnit(fileMain); + assertNotNull(oldCu); + + GlobalCaches.CacheKey oldKey = new GlobalCaches.CacheKey(oldCu, "foo", GlobalCaches.LookupType.FUNC); + GlobalCaches.lookupCache.put(oldKey, oldCu); + + manager.removeCompilationUnit(fileMain); + + assertEquals(GlobalCaches.lookupCache.containsKey(oldKey), false, + "removing a CU must drop lookup keys that keep the old AST alive"); + } + @Test public void duplicatePackageReportsSingleClearMessage() throws IOException { File projectFolder = new File("./temp/testProject_duplicate_package_message/");