Skip to content

Commit 1d9650e

Browse files
authored
Correctly resolve nested module usage, fixes #846 (#1106)
1 parent a7dd188 commit 1d9650e

File tree

3 files changed

+173
-25
lines changed

3 files changed

+173
-25
lines changed

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/names/NameResolution.java

Lines changed: 128 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -267,39 +267,150 @@ public static NameLink lookupMemberVar(Element node, WurstType receiverType, Str
267267
scope = nextScope(scope);
268268
}
269269

270+
DefLinkMatch bestMatch = null;
271+
270272
for (WScope s : scopes) {
271273
Collection<DefLink> links = s.attrNameLinks().get(name);
272274
if (links.isEmpty()) continue;
273275

274-
for (DefLink n : links) {
275-
if (!(n instanceof VarLink)) {
276-
continue;
277-
}
278-
DefLink n2 = matchDefLinkReceiver(n, receiverType, node, showErrors);
279-
if (n2 != null) {
280-
if (!showErrors) {
281-
GlobalCaches.CacheKey key = new GlobalCaches.CacheKey(node, name + "@" + receiverType, GlobalCaches.LookupType.MEMBER_VAR);
282-
GlobalCaches.lookupCache.put(key, n2);
276+
DefLinkMatch candidate = findBestMemberVarMatch(links, receiverType, node, showErrors);
277+
if (candidate != null) {
278+
if (bestMatch == null || candidate.distance < bestMatch.distance) {
279+
bestMatch = candidate;
280+
if (bestMatch.distance == 0) {
281+
break;
283282
}
284-
return n2;
285283
}
286284
}
287285
}
288286

287+
if (bestMatch != null) {
288+
if (!showErrors) {
289+
GlobalCaches.CacheKey key = new GlobalCaches.CacheKey(node, name + "@" + receiverType, GlobalCaches.LookupType.MEMBER_VAR);
290+
GlobalCaches.lookupCache.put(key, bestMatch.link);
291+
}
292+
return bestMatch.link;
293+
}
294+
289295
if (receiverType instanceof WurstTypeClassOrInterface) {
290296
WurstTypeClassOrInterface ct = (WurstTypeClassOrInterface) receiverType;
291-
for (DefLink n : ct.nameLinks().get(name)) {
292-
if (n instanceof VarLink || n instanceof TypeDefLink) {
293-
if (n.getVisibility().isPublic()) {
294-
return n;
295-
}
297+
Collection<DefLink> typeNameLinks = ct.nameLinks().get(name);
298+
DefLinkMatch candidate = findBestMemberVarMatch(typeNameLinks, receiverType, node, showErrors);
299+
if (candidate != null && candidate.link.getVisibility().isPublic()) {
300+
return candidate.link;
301+
}
302+
for (DefLink n : typeNameLinks) {
303+
if (n instanceof TypeDefLink && n.getVisibility().isPublic()) {
304+
return n;
296305
}
297306
}
298307
}
299308

300309
return null;
301310
}
302311

312+
private static @Nullable DefLinkMatch findBestMemberVarMatch(Collection<DefLink> links, WurstType receiverType, Element node, boolean showErrors) {
313+
DefLink bestLink = null;
314+
int bestDistance = Integer.MAX_VALUE;
315+
316+
for (DefLink n : links) {
317+
if (!(n instanceof VarLink)) {
318+
continue;
319+
}
320+
DefLink matched = matchDefLinkReceiver(n, receiverType, node, showErrors);
321+
if (matched == null) {
322+
continue;
323+
}
324+
int distance = receiverDistance(receiverType, matched.getReceiverType(), node);
325+
if (distance < bestDistance) {
326+
bestLink = matched;
327+
bestDistance = distance;
328+
if (distance == 0) {
329+
break;
330+
}
331+
}
332+
}
333+
334+
if (bestLink == null) {
335+
return null;
336+
}
337+
return new DefLinkMatch(bestLink, bestDistance);
338+
}
339+
340+
private static int receiverDistance(WurstType receiverType, @Nullable WurstType candidateType, Element node) {
341+
if (candidateType == null) {
342+
return Integer.MAX_VALUE;
343+
}
344+
345+
if (receiverType.equalsType(candidateType, node)) {
346+
return 0;
347+
}
348+
349+
ClassDef receiverClass = owningClass(receiverType);
350+
ClassDef candidateClass = owningClass(candidateType);
351+
if (receiverClass != null && candidateClass != null) {
352+
int distance = inheritanceDistance(receiverClass, candidateClass);
353+
if (distance >= 0) {
354+
return distance;
355+
}
356+
}
357+
358+
return Integer.MAX_VALUE / 2;
359+
}
360+
361+
private static int inheritanceDistance(ClassDef start, ClassDef target) {
362+
int distance = 0;
363+
ClassDef current = start;
364+
while (current != null) {
365+
if (current == target) {
366+
return distance;
367+
}
368+
OptTypeExpr extended = current.getExtendedClass();
369+
if (!(extended instanceof TypeExpr)) {
370+
break;
371+
}
372+
WurstType extendedType = ((TypeExpr) extended).attrTyp();
373+
if (!(extendedType instanceof WurstTypeClass)) {
374+
break;
375+
}
376+
current = ((WurstTypeClass) extendedType).getClassDef();
377+
distance++;
378+
}
379+
return -1;
380+
}
381+
382+
private static @Nullable ClassDef owningClass(WurstType type) {
383+
if (type instanceof WurstTypeClass) {
384+
return ((WurstTypeClass) type).getClassDef();
385+
}
386+
if (type instanceof WurstTypeClassOrInterface) {
387+
ClassOrInterface def = ((WurstTypeClassOrInterface) type).getDef();
388+
if (def instanceof ClassDef) {
389+
return (ClassDef) def;
390+
}
391+
return null;
392+
}
393+
if (type instanceof WurstTypeModuleInstanciation) {
394+
NamedScope inst = ((WurstTypeModuleInstanciation) type).getDef();
395+
return inst.attrNearestClassDef();
396+
}
397+
if (type instanceof WurstTypeModule) {
398+
ModuleDef moduleDef = ((WurstTypeModule) type).getDef();
399+
return moduleDef.attrNearestClassDef();
400+
}
401+
return null;
402+
}
403+
404+
private static final class DefLinkMatch {
405+
private final DefLink link;
406+
private final int distance;
407+
408+
private DefLinkMatch(DefLink link, int distance) {
409+
this.link = link;
410+
this.distance = distance;
411+
}
412+
}
413+
303414
public static DefLink matchDefLinkReceiver(DefLink n, WurstType receiverType, Element node, boolean showErrors) {
304415
WurstType n_receiverType = n.getReceiverType();
305416
if (n_receiverType == null) {
@@ -312,7 +423,7 @@ public static DefLink matchDefLinkReceiver(DefLink n, WurstType receiverType, El
312423
if (showErrors) {
313424
if (n.getVisibility() == Visibility.PRIVATE_OTHER) {
314425
node.addError(Utils.printElement(n.getDef()) + " is private and cannot be used here.");
315-
} else if (n.getVisibility() == Visibility.PROTECTED_OTHER) {
426+
} else if (n.getVisibility() == Visibility.PROTECTED_OTHER && !receiverType.isSubtypeOf(n_receiverType, node)) {
316427
node.addError(Utils.printElement(n.getDef()) + " is protected and cannot be used here.");
317428
}
318429
}
@@ -339,6 +450,7 @@ public static DefLink matchDefLinkReceiver(DefLink n, WurstType receiverType, El
339450
scope = nextScope(scope);
340451
}
341452

453+
342454
for (WScope s : scopes) {
343455
ImmutableCollection<TypeLink> links = s.attrTypeNameLinks().get(name);
344456
if (links.isEmpty()) continue;

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/validation/WurstValidator.java

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1257,15 +1257,7 @@ private void checkAssignment(boolean isJassCode, Element pos, WurstType leftType
12571257
if (rightType instanceof WurstTypeVoid) {
12581258
if (pos.attrNearestPackage() instanceof WPackage) {
12591259
WPackage pack = (WPackage) pos.attrNearestPackage();
1260-
if (pack != null && !pack.getName().equals("WurstREPL")) { // allow
1261-
// assigning
1262-
// nothing
1263-
// to
1264-
// a
1265-
// variable
1266-
// in
1267-
// the
1268-
// Repl
1260+
if (pack != null && !pack.getName().equals("WurstREPL")) { // allow assigning nothing to a variable in the Repl
12691261
pos.addError("Function or expression returns nothing. Cannot assign nothing to a variable.");
12701262
}
12711263
}

de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/BugTests.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1636,5 +1636,49 @@ public void moduleInOtherPackage() {
16361636
);
16371637
}
16381638

1639+
@Test
1640+
public void linkedListModule_perClassStatics_andTyping_ok() {
1641+
testAssertOkLinesWithStdLib(true,
1642+
"package Test",
1643+
"import LinkedListModule",
1644+
"",
1645+
"native println(string s)",
1646+
"",
1647+
"class A",
1648+
" use LinkedListModule",
1649+
"",
1650+
"class B extends A",
1651+
" use LinkedListModule",
1652+
"",
1653+
"class C extends A",
1654+
"",
1655+
"class D extends C",
1656+
" use LinkedListModule",
1657+
"",
1658+
"function doSmth()",
1659+
" // Iteration should compile for any class that uses LinkedListModule:",
1660+
" for a in A",
1661+
" // ok: iterates A list",
1662+
" for b in B",
1663+
" // ok: iterates B list (distinct from A)",
1664+
"",
1665+
" // Accessing class statics should be typed to that concrete class:",
1666+
" B b2 = B.first // must typecheck as B, not A",
1667+
" A a2 = A.first // must typecheck as A",
1668+
"",
1669+
" // D extends C but only D uses the module; typing must be D:",
1670+
" var d = D.first // type D",
1671+
" while d != null",
1672+
" d = d.next // type D",
1673+
"",
1674+
"init",
1675+
" // We don’t rely on runtime behavior here—this test targets typing/name resolution.",
1676+
" // If all lines above typecheck and run, we count it as success:",
1677+
" doSmth()",
1678+
" testSuccess()"
1679+
);
1680+
}
1681+
1682+
16391683

16401684
}

0 commit comments

Comments
 (0)