Skip to content

Commit 691fdf2

Browse files
authored
Correctly collect overloads in class hierachy, fixes #863 (#1101)
1 parent c47755f commit 691fdf2

File tree

5 files changed

+393
-70
lines changed

5 files changed

+393
-70
lines changed

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/AttrFuncDef.java

Lines changed: 95 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import de.peeeq.wurstscript.WurstOperator;
77
import de.peeeq.wurstscript.ast.*;
88
import de.peeeq.wurstscript.attributes.names.FuncLink;
9+
import de.peeeq.wurstscript.attributes.names.NameResolution;
910
import de.peeeq.wurstscript.attributes.names.Visibility;
1011
import de.peeeq.wurstscript.types.*;
1112
import de.peeeq.wurstscript.utils.Utils;
@@ -18,6 +19,9 @@
1819
import java.util.function.Predicate;
1920
import java.util.stream.Collectors;
2021

22+
import static de.peeeq.wurstscript.attributes.AttrPossibleFunctionSignatures.*;
23+
import static de.peeeq.wurstscript.attributes.names.NameResolution.lookupMemberFuncs;
24+
2125

2226
/**
2327
* this attribute find the variable definition for every variable reference
@@ -65,18 +69,102 @@ public static FuncLink calculate(final ExprFuncRef node) {
6569
}
6670

6771
public static @Nullable FuncLink calculate(final ExprMemberMethod node) {
72+
WurstType recvT = node.getLeft().attrTyp();
73+
var raw = NameResolution.lookupMemberFuncs(node, recvT, node.getFuncName(), /*showErrors=*/false);
6874

69-
Expr left = node.getLeft();
70-
WurstType leftType = left.attrTyp();
71-
String funcName = node.getFuncName();
75+
java.util.ArrayList<FuncLink> visible = new java.util.ArrayList<>(raw.size());
76+
java.util.ArrayList<FuncLink> hidden = new java.util.ArrayList<>(raw.size());
77+
for (var f : raw) {
78+
if (isVisible(f)) visible.add(f); else hidden.add(f);
79+
}
7280

73-
@Nullable FuncLink result = searchMemberFunc(node, leftType, funcName, argumentTypes(node));
74-
if (result == null) {
75-
node.addError("The method " + funcName + " is undefined for receiver of type " + leftType);
81+
if (!raw.isEmpty() && visible.isEmpty()) {
82+
// Keep the classic diagnostic the tests look for:
83+
node.addError("The method " + node.getFuncName() + " is not visible here.");
84+
return null; // don’t leak a def to downstream passes/codegen
7685
}
77-
return result;
86+
87+
java.util.List<FuncLink> methods = new java.util.ArrayList<>();
88+
java.util.List<FuncLink> exts = new java.util.ArrayList<>();
89+
for (var f : visible) {
90+
if (isExtension(f)) exts.add(f); else methods.add(f);
91+
}
92+
93+
if (!exts.isEmpty()) {
94+
exts = keepMostSpecificReceivers(exts, FuncLink::getReceiverType, node);
95+
}
96+
97+
java.util.ArrayList<FuncLink> cands = new java.util.ArrayList<>(methods.size() + exts.size());
98+
cands.addAll(methods);
99+
cands.addAll(exts);
100+
101+
var argTypes = AttrFuncDef.argumentTypesPre(node);
102+
103+
// Pass 1: exact matches
104+
java.util.ArrayList<FuncLink> exactLinks = new java.util.ArrayList<>();
105+
java.util.ArrayList<FunctionSignature> exactSigs = new java.util.ArrayList<>();
106+
for (var f : cands) {
107+
var sig = FunctionSignature.fromNameLink(f);
108+
var m = sig.matchAgainstArgs(argTypes, node);
109+
if (m != null) {
110+
exactLinks.add(f);
111+
exactSigs.add(m);
112+
}
113+
}
114+
if (!exactLinks.isEmpty()) {
115+
// methods vs others
116+
java.util.ArrayList<Integer> methodIdxs = new java.util.ArrayList<>();
117+
for (int i = 0; i < exactLinks.size(); i++) {
118+
if (!isExtension(exactLinks.get(i))) methodIdxs.add(i);
119+
}
120+
if (methodIdxs.size() > 1) {
121+
// filter method candidates by most specific receiver
122+
java.util.ArrayList<FunctionSignature> methSigs = new java.util.ArrayList<>();
123+
for (int i : methodIdxs) methSigs.add(exactSigs.get(i));
124+
methSigs = (java.util.ArrayList<FunctionSignature>) keepMostSpecificReceivers(
125+
methSigs, FunctionSignature::getReceiverType, node
126+
);
127+
// pick the first of the survivors
128+
var chosenSig = methSigs.get(0);
129+
// find corresponding link
130+
for (int i = 0; i < exactSigs.size(); i++) {
131+
if (exactSigs.get(i) == chosenSig) {
132+
return exactLinks.get(i).withTypeArgBinding(node, chosenSig.getMapping());
133+
}
134+
}
135+
} else if (methodIdxs.size() == 1) {
136+
int i = methodIdxs.get(0);
137+
return exactLinks.get(i).withTypeArgBinding(node, exactSigs.get(i).getMapping());
138+
} else {
139+
// no methods, only extensions exact → pick first (they were narrowed already)
140+
return exactLinks.get(0).withTypeArgBinding(node, exactSigs.get(0).getMapping());
141+
}
142+
}
143+
144+
// Pass 2: best-effort (unchanged)
145+
int bestBad = Integer.MAX_VALUE;
146+
FuncLink best = null;
147+
FunctionSignature bestSig = null;
148+
for (var f : cands) {
149+
var sig = FunctionSignature.fromNameLink(f);
150+
var r = sig.tryMatchAgainstArgs(argTypes, node.getArgs(), node);
151+
if (r.getBadness() < bestBad) {
152+
bestBad = r.getBadness();
153+
best = f;
154+
bestSig = r.getSig();
155+
}
156+
}
157+
return best == null ? null : best.withTypeArgBinding(node, bestSig.getMapping());
158+
}
159+
160+
161+
162+
public static @Nullable FunctionDefinition calculateDef(final ExprMemberMethod node) {
163+
var fl = node.attrFuncLink();
164+
return fl == null ? null : fl.getDef();
78165
}
79166

167+
80168
public static @Nullable FuncLink calculate(final ExprFunctionCall node) {
81169
FuncLink result = searchFunction(node.getFuncName(), node, argumentTypes(node));
82170

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/AttrFunctionSignature.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,25 @@ public class AttrFunctionSignature {
1818
public static FunctionSignature calculate(StmtCall fc) {
1919
Collection<FunctionSignature> sigs = fc.attrPossibleFunctionSignatures();
2020
FunctionSignature sig = filterSigs(sigs, argTypes(fc), fc);
21+
2122
VariableBinding mapping = sig.getMapping();
2223
for (CompileError error : mapping.getErrors()) {
2324
fc.getErrorHandler().sendError(error);
2425
}
25-
if (mapping.hasUnboundTypeVars()) {
26+
27+
// If any argument is a closure, let it be typed using the selected signature’s
28+
// expected parameter types before complaining about unbound type variables.
29+
boolean hasClosureArg = false;
30+
if (fc instanceof AstElementWithArgs) {
31+
for (Expr a : ((AstElementWithArgs) fc).getArgs()) {
32+
if (a instanceof ExprClosure) {
33+
hasClosureArg = true;
34+
break;
35+
}
36+
}
37+
}
38+
39+
if (mapping.hasUnboundTypeVars() && !hasClosureArg) {
2640
fc.addError("Cannot infer type for type parameter " + mapping.printUnboundTypeVars());
2741
}
2842

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,41 @@
11
package de.peeeq.wurstscript.attributes;
22

3-
import de.peeeq.wurstscript.ast.ModuleDef;
4-
import de.peeeq.wurstscript.ast.ModuleInstanciation;
5-
import de.peeeq.wurstscript.ast.TypeDef;
3+
import de.peeeq.wurstscript.ast.*;
64
import de.peeeq.wurstscript.utils.Utils;
75
import org.eclipse.jdt.annotation.Nullable;
86

9-
public class AttrModuleInstanciations {
7+
public final class AttrModuleInstanciations {
8+
9+
private AttrModuleInstanciations() {}
1010

1111
public static @Nullable ModuleDef getModuleOrigin(ModuleInstanciation mi) {
12-
TypeDef def = mi.getParent().lookupType(mi.getName());
13-
if (def instanceof ModuleDef) {
14-
return (ModuleDef) def;
15-
} else {
12+
// NOTE: For ModuleInstanciation the "name" used for resolution has historically been getName().
13+
// Keep this to preserve prior behavior.
14+
final String name = mi.getName();
15+
16+
// 1) Normal path: resolve relative to the lexical parent (old behavior)
17+
final Element parent = mi.getParent();
18+
if (parent != null) {
19+
TypeDef def = parent.lookupType(name, /*showErrors*/ false);
20+
if (def instanceof ModuleDef) {
21+
return (ModuleDef) def;
22+
}
23+
// Attached but not found -> keep the old error
1624
mi.addError("Could not find module origin for " + Utils.printElement(mi));
25+
return null;
26+
}
27+
28+
// 2) Detached during incremental build: try the nearest attached scope
29+
final WScope scope = mi.attrNearestScope();
30+
if (scope != null) {
31+
TypeDef def = scope.lookupType(name, /*showErrors*/ false);
32+
if (def instanceof ModuleDef) {
33+
return (ModuleDef) def;
34+
}
1735
}
36+
37+
// 3) Still not found and we're detached: this can be a transient state,
38+
// so don't emit an error here. Return null and let callers handle gracefully.
1839
return null;
1940
}
20-
2141
}

0 commit comments

Comments
 (0)