Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -267,39 +267,150 @@ public static NameLink lookupMemberVar(Element node, WurstType receiverType, Str
scope = nextScope(scope);
}

DefLinkMatch bestMatch = null;

for (WScope s : scopes) {
Collection<DefLink> links = s.attrNameLinks().get(name);
if (links.isEmpty()) continue;

for (DefLink n : links) {
if (!(n instanceof VarLink)) {
continue;
}
DefLink n2 = matchDefLinkReceiver(n, receiverType, node, showErrors);
if (n2 != null) {
if (!showErrors) {
GlobalCaches.CacheKey key = new GlobalCaches.CacheKey(node, name + "@" + receiverType, GlobalCaches.LookupType.MEMBER_VAR);
GlobalCaches.lookupCache.put(key, n2);
DefLinkMatch candidate = findBestMemberVarMatch(links, receiverType, node, showErrors);
if (candidate != null) {
if (bestMatch == null || candidate.distance < bestMatch.distance) {
bestMatch = candidate;
if (bestMatch.distance == 0) {
break;
}
return n2;
}
}
}

if (bestMatch != null) {
if (!showErrors) {
GlobalCaches.CacheKey key = new GlobalCaches.CacheKey(node, name + "@" + receiverType, GlobalCaches.LookupType.MEMBER_VAR);
GlobalCaches.lookupCache.put(key, bestMatch.link);
}
return bestMatch.link;
}

if (receiverType instanceof WurstTypeClassOrInterface) {
WurstTypeClassOrInterface ct = (WurstTypeClassOrInterface) receiverType;
for (DefLink n : ct.nameLinks().get(name)) {
if (n instanceof VarLink || n instanceof TypeDefLink) {
if (n.getVisibility().isPublic()) {
return n;
}
Collection<DefLink> typeNameLinks = ct.nameLinks().get(name);
DefLinkMatch candidate = findBestMemberVarMatch(typeNameLinks, receiverType, node, showErrors);
if (candidate != null && candidate.link.getVisibility().isPublic()) {
return candidate.link;
}
for (DefLink n : typeNameLinks) {
if (n instanceof TypeDefLink && n.getVisibility().isPublic()) {
return n;
}
}
}

return null;
}

private static @Nullable DefLinkMatch findBestMemberVarMatch(Collection<DefLink> links, WurstType receiverType, Element node, boolean showErrors) {
DefLink bestLink = null;
int bestDistance = Integer.MAX_VALUE;

for (DefLink n : links) {
if (!(n instanceof VarLink)) {
continue;
}
DefLink matched = matchDefLinkReceiver(n, receiverType, node, showErrors);
if (matched == null) {
continue;
}
int distance = receiverDistance(receiverType, matched.getReceiverType(), node);
if (distance < bestDistance) {
bestLink = matched;
bestDistance = distance;
if (distance == 0) {
break;
}
}
}

if (bestLink == null) {
return null;
}
return new DefLinkMatch(bestLink, bestDistance);
}

private static int receiverDistance(WurstType receiverType, @Nullable WurstType candidateType, Element node) {
if (candidateType == null) {
return Integer.MAX_VALUE;
}

if (receiverType.equalsType(candidateType, node)) {
return 0;
}

ClassDef receiverClass = owningClass(receiverType);
ClassDef candidateClass = owningClass(candidateType);
if (receiverClass != null && candidateClass != null) {
int distance = inheritanceDistance(receiverClass, candidateClass);
if (distance >= 0) {
return distance;
}
}

return Integer.MAX_VALUE / 2;
}

private static int inheritanceDistance(ClassDef start, ClassDef target) {
int distance = 0;
ClassDef current = start;
while (current != null) {
if (current == target) {
return distance;
}
OptTypeExpr extended = current.getExtendedClass();
if (!(extended instanceof TypeExpr)) {
break;
}
WurstType extendedType = ((TypeExpr) extended).attrTyp();
if (!(extendedType instanceof WurstTypeClass)) {
break;
}
current = ((WurstTypeClass) extendedType).getClassDef();
distance++;
}
return -1;
}

private static @Nullable ClassDef owningClass(WurstType type) {
if (type instanceof WurstTypeClass) {
return ((WurstTypeClass) type).getClassDef();
}
if (type instanceof WurstTypeClassOrInterface) {
ClassOrInterface def = ((WurstTypeClassOrInterface) type).getDef();
if (def instanceof ClassDef) {
return (ClassDef) def;
}
return null;
}
if (type instanceof WurstTypeModuleInstanciation) {
NamedScope inst = ((WurstTypeModuleInstanciation) type).getDef();
return inst.attrNearestClassDef();
}
if (type instanceof WurstTypeModule) {
ModuleDef moduleDef = ((WurstTypeModule) type).getDef();
return moduleDef.attrNearestClassDef();
}
return null;
}

private static final class DefLinkMatch {
private final DefLink link;
private final int distance;

private DefLinkMatch(DefLink link, int distance) {
this.link = link;
this.distance = distance;
}
}

public static DefLink matchDefLinkReceiver(DefLink n, WurstType receiverType, Element node, boolean showErrors) {
WurstType n_receiverType = n.getReceiverType();
if (n_receiverType == null) {
Expand All @@ -312,7 +423,7 @@ public static DefLink matchDefLinkReceiver(DefLink n, WurstType receiverType, El
if (showErrors) {
if (n.getVisibility() == Visibility.PRIVATE_OTHER) {
node.addError(Utils.printElement(n.getDef()) + " is private and cannot be used here.");
} else if (n.getVisibility() == Visibility.PROTECTED_OTHER) {
} else if (n.getVisibility() == Visibility.PROTECTED_OTHER && !receiverType.isSubtypeOf(n_receiverType, node)) {
node.addError(Utils.printElement(n.getDef()) + " is protected and cannot be used here.");
}
}
Expand All @@ -339,6 +450,7 @@ public static DefLink matchDefLinkReceiver(DefLink n, WurstType receiverType, El
scope = nextScope(scope);
}


for (WScope s : scopes) {
ImmutableCollection<TypeLink> links = s.attrTypeNameLinks().get(name);
if (links.isEmpty()) continue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1257,15 +1257,7 @@ private void checkAssignment(boolean isJassCode, Element pos, WurstType leftType
if (rightType instanceof WurstTypeVoid) {
if (pos.attrNearestPackage() instanceof WPackage) {
WPackage pack = (WPackage) pos.attrNearestPackage();
if (pack != null && !pack.getName().equals("WurstREPL")) { // allow
// assigning
// nothing
// to
// a
// variable
// in
// the
// Repl
if (pack != null && !pack.getName().equals("WurstREPL")) { // allow assigning nothing to a variable in the Repl
pos.addError("Function or expression returns nothing. Cannot assign nothing to a variable.");
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1636,5 +1636,49 @@ public void moduleInOtherPackage() {
);
}

@Test
public void linkedListModule_perClassStatics_andTyping_ok() {
testAssertOkLinesWithStdLib(true,
"package Test",
"import LinkedListModule",
"",
"native println(string s)",
"",
"class A",
" use LinkedListModule",
"",
"class B extends A",
" use LinkedListModule",
"",
"class C extends A",
"",
"class D extends C",
" use LinkedListModule",
"",
"function doSmth()",
" // Iteration should compile for any class that uses LinkedListModule:",
" for a in A",
" // ok: iterates A list",
" for b in B",
" // ok: iterates B list (distinct from A)",
"",
" // Accessing class statics should be typed to that concrete class:",
" B b2 = B.first // must typecheck as B, not A",
" A a2 = A.first // must typecheck as A",
"",
" // D extends C but only D uses the module; typing must be D:",
" var d = D.first // type D",
" while d != null",
" d = d.next // type D",
"",
"init",
" // We don’t rely on runtime behavior here—this test targets typing/name resolution.",
" // If all lines above typecheck and run, we count it as success:",
" doSmth()",
" testSuccess()"
);
}



}
Loading