Skip to content

Commit 57218bf

Browse files
authored
Find matching interface implementations in class hierarchy, fixes #898 (#1107)
1 parent 1d9650e commit 57218bf

File tree

7 files changed

+223
-19
lines changed

7 files changed

+223
-19
lines changed

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,11 @@ private static FunctionSignature filterSigs(
6969
return candidates.get(0);
7070
}
7171

72+
candidates = filterPreferNonAbstract(candidates);
73+
if (candidates.size() == 1) {
74+
return candidates.get(0);
75+
}
76+
7277

7378
boolean b = true;
7479
for (WurstType t : argTypes) {
@@ -99,6 +104,20 @@ private static List<FunctionSignature> filterByIfNotDefinedAnnotation(List<Funct
99104
return list;
100105
}
101106

107+
private static List<FunctionSignature> filterPreferNonAbstract(List<FunctionSignature> candidates) {
108+
List<FunctionSignature> nonAbstract = new ArrayList<>();
109+
for (FunctionSignature sig : candidates) {
110+
FunctionDefinition def = sig.getDef();
111+
if (def != null && !def.attrIsAbstract()) {
112+
nonAbstract.add(sig);
113+
}
114+
}
115+
if (!nonAbstract.isEmpty()) {
116+
return nonAbstract;
117+
}
118+
return candidates;
119+
}
120+
102121
@NotNull
103122
private static List<FunctionSignature> filterByArgumentTypes(Collection<FunctionSignature> sigs, List<WurstType> argTypes, StmtCall location) {
104123
List<FunctionSignature> candidates = new ArrayList<>();

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

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ static private class OverrideCheckResult {
2828
// (for interfaces override modifier is optional)
2929
boolean requiresOverrideMod = false;
3030

31+
// inherited functions used for override checks only, skip reporting
32+
boolean skipErrorReporting = false;
33+
3134
// errors for functions with same name that it does not override
3235
io.vavr.collection.List<String> overrideErrors = io.vavr.collection.List.empty();
3336

@@ -71,6 +74,9 @@ private static void reportOverrideErrors(Map<String, Map<FuncLink, OverrideCheck
7174
for (Entry<FuncLink, OverrideCheckResult> e : map.entrySet()) {
7275
FunctionDefinition f = e.getKey().getDef();
7376
OverrideCheckResult check = e.getValue();
77+
if (check.skipErrorReporting) {
78+
continue;
79+
}
7480
if (f.attrIsOverride() && !check.doesOverride) {
7581
StringBuilder msg = new StringBuilder("Function " + f.getName() + " does not override anything.");
7682
for (String overrideError : check.overrideErrors) {
@@ -101,30 +107,36 @@ public static ImmutableMultimap<String, DefLink> calculate(InterfaceDef i) {
101107

102108
private static void addNamesFromExtendedInterfaces(Multimap<String, DefLink> result, WurstTypeInterface iType, Map<String, Map<FuncLink, OverrideCheckResult>> overrideCheckResults) {
103109
for (WurstTypeInterface superI : iType.extendedInterfaces()) {
104-
addNewNameLinks(result, overrideCheckResults, superI.nameLinks(), false);
110+
addNewNameLinks(result, overrideCheckResults, superI.nameLinks(), false, null);
105111
}
106112
}
107113

108114

109115
private static void addNamesFromImplementedInterfaces(Multimap<String, DefLink> result, WurstTypeClass classDef, Map<String, Map<FuncLink, OverrideCheckResult>> overrideCheckResults) {
110116
for (WurstTypeInterface interfaceType : classDef.implementedInterfaces()) {
111-
addNewNameLinks(result, overrideCheckResults, interfaceType.nameLinks(), false);
117+
addNewNameLinks(result, overrideCheckResults, interfaceType.nameLinks(), false, classDef.getClassDef());
112118
}
113119
}
114120

115121
private static void addNamesFromSuperClass(Multimap<String, DefLink> result, WurstTypeClass c, Map<String, Map<FuncLink, OverrideCheckResult>> overrideCheckResults) {
116122
@Nullable WurstTypeClass superClass = c.extendedClass();
117123
if (superClass != null) {
118-
addNewNameLinks(result, overrideCheckResults, superClass.nameLinks(), false);
124+
addNewNameLinks(result, overrideCheckResults, superClass.nameLinks(), false, c.getClassDef());
119125
}
120126
}
121127

122-
private static void addNewNameLinks(Multimap<String, DefLink> result, Map<String, Map<FuncLink, OverrideCheckResult>> overrideCheckResults, ImmutableMultimap<String, DefLink> newNameLinks, boolean allowStaticOverride) {
128+
private static void addNewNameLinks(Multimap<String, DefLink> result, Map<String, Map<FuncLink, OverrideCheckResult>> overrideCheckResults, ImmutableMultimap<String, DefLink> newNameLinks, boolean allowStaticOverride, @Nullable ClassDef currentClass) {
123129
for (Entry<String, DefLink> e : newNameLinks.entries()) {
124130
DefLink def = e.getValue();
125131
if (!def.getVisibility().isInherited()) {
126132
continue;
127133
}
134+
if (currentClass != null && def instanceof FuncLink) {
135+
DefLink adapted = ((FuncLink) def).adaptToReceiverType(currentClass.attrTyp());
136+
if (adapted != null) {
137+
def = adapted;
138+
}
139+
}
128140
String name = e.getKey();
129141
boolean isOverridden = false;
130142
if (def instanceof FuncLink) {
@@ -153,8 +165,41 @@ private static void addNewNameLinks(Multimap<String, DefLink> result, Map<String
153165
}
154166
if (!isOverridden) {
155167
result.put(name, def.hidingPrivate());
168+
if (def instanceof FuncLink) {
169+
registerOverrideCandidate(overrideCheckResults, name, (FuncLink) def, currentClass);
170+
}
171+
}
172+
}
173+
}
174+
175+
private static void registerOverrideCandidate(Map<String, Map<FuncLink, OverrideCheckResult>> overrideCheckResults,
176+
String name, FuncLink funcLink, @Nullable ClassDef currentClass) {
177+
if (currentClass == null) {
178+
return;
179+
}
180+
FunctionDefinition def = funcLink.getDef();
181+
if (!(def instanceof FuncDef)) {
182+
return;
183+
}
184+
if (def.attrIsAbstract()) {
185+
return;
186+
}
187+
if (def.attrNearestClassDef() == null) {
188+
return;
189+
}
190+
if (currentClass != null) {
191+
DefLink adapted = funcLink.adaptToReceiverType(currentClass.attrTyp());
192+
if (adapted instanceof FuncLink) {
193+
funcLink = (FuncLink) adapted;
156194
}
157195
}
196+
Map<FuncLink, OverrideCheckResult> map = overrideCheckResults.computeIfAbsent(name, s -> new HashMap<>());
197+
OverrideCheckResult check = map.get(funcLink);
198+
if (check == null) {
199+
check = new OverrideCheckResult();
200+
check.skipErrorReporting = true;
201+
map.put(funcLink, check);
202+
}
158203
}
159204

160205

@@ -319,7 +364,7 @@ private static void addNameDefDefLink(Multimap<String, DefLink> result, NameDef
319364
private static void addNamesFromUsedModuleInstantiations(ClassOrModuleOrModuleInstanciation c,
320365
Multimap<String, DefLink> result, Map<String, Map<FuncLink, OverrideCheckResult>> overrideCheckResults) {
321366
for (ModuleInstanciation m : c.getModuleInstanciations()) {
322-
addNewNameLinks(result, overrideCheckResults, m.attrNameLinks(), true);
367+
addNewNameLinks(result, overrideCheckResults, m.attrNameLinks(), true, null);
323368
}
324369
}
325370

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/ImTranslator.java

Lines changed: 59 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1129,23 +1129,70 @@ public Map<ClassDef, FuncDef> getClassesWithImplementation(Collection<ClassDef>
11291129
+ " in "
11301130
+ Utils.printElementWithSource(Optional.of(c)));
11311131
}
1132-
for (NameLink nameLink : c.attrNameLinks().get(func.getName())) {
1133-
NameDef nameDef = nameLink.getDef();
1134-
if (nameLink.getDefinedIn() == c) {
1135-
if (nameLink instanceof FuncLink && nameLink.getDef() instanceof FuncDef) {
1136-
FuncLink funcLink = (FuncLink) nameLink;
1137-
FuncDef f = (FuncDef) funcLink.getDef();
1138-
// check if function f overrides func
1139-
if (WurstValidator.canOverride(funcLink, funcNameLink, false)) {
1140-
result.put(c, f);
1141-
}
1142-
}
1143-
}
1132+
FuncLink implementationLink = findImplementationLink(c, funcNameLink);
1133+
if (implementationLink != null && implementationLink.getDef() instanceof FuncDef) {
1134+
result.put(c, (FuncDef) implementationLink.getDef());
11441135
}
11451136
}
11461137
return result;
11471138
}
11481139

1140+
private @Nullable FuncLink findImplementationLink(ClassDef c, FuncLink target) {
1141+
FuncLink best = null;
1142+
int bestDistance = Integer.MAX_VALUE;
1143+
for (NameLink candidateLink : c.attrNameLinks().get(target.getName())) {
1144+
if (!(candidateLink instanceof FuncLink)) {
1145+
continue;
1146+
}
1147+
FuncLink candidate = (FuncLink) candidateLink;
1148+
if (!WurstValidator.canOverride(candidate, target, false)) {
1149+
continue;
1150+
}
1151+
FunctionDefinition candidateDef = candidate.getDef();
1152+
if (!(candidateDef instanceof FuncDef)) {
1153+
continue;
1154+
}
1155+
if (candidateDef.attrIsAbstract()) {
1156+
continue;
1157+
}
1158+
ClassDef owner = candidateDef.attrNearestClassDef();
1159+
if (owner == null) {
1160+
continue;
1161+
}
1162+
int distance = distanceToOwner(c, owner);
1163+
if (distance == Integer.MAX_VALUE) {
1164+
continue;
1165+
}
1166+
if (best == null || distance < bestDistance) {
1167+
best = candidate;
1168+
bestDistance = distance;
1169+
}
1170+
}
1171+
return best;
1172+
}
1173+
1174+
private int distanceToOwner(ClassDef start, ClassDef owner) {
1175+
int distance = 0;
1176+
ClassDef current = start;
1177+
Set<ClassDef> visited = new HashSet<>();
1178+
while (current != null && visited.add(current)) {
1179+
if (current == owner) {
1180+
return distance;
1181+
}
1182+
WurstTypeClass type = current.attrTypC();
1183+
if (type == null) {
1184+
break;
1185+
}
1186+
WurstTypeClass superType = type.extendedClass();
1187+
if (superType == null) {
1188+
break;
1189+
}
1190+
current = superType.getClassDef();
1191+
distance++;
1192+
}
1193+
return Integer.MAX_VALUE;
1194+
}
1195+
11491196

11501197
private final Map<ClassDef, List<Pair<ImVar, VarInitialization>>> classDynamicInitMap = Maps.newLinkedHashMap();
11511198
private final Map<ClassDef, List<WStatement>> classInitStatements = Maps.newLinkedHashMap();

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/OverrideUtils.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ public static void addOverride(
7878
}
7979

8080
if (!needConversion) {
81+
if (superMethodIm == subMethod) {
82+
return;
83+
}
8184
superMethodIm.getSubMethods().add(subMethod);
8285
return;
8386
}

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/types/FunctionSignature.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,13 @@ public WurstType getReturnType() {
6161
return receiverType;
6262
}
6363

64+
public @Nullable FunctionDefinition getDef() {
65+
if (trace instanceof FunctionDefinition) {
66+
return (FunctionDefinition) trace;
67+
}
68+
return null;
69+
}
70+
6471
@CheckReturnValue
6572
public FunctionSignature setTypeArgs(Element context, VariableBinding newMapping) {
6673
if (newMapping.isEmpty()) {

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

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -525,8 +525,11 @@ private void checkAbstractMethods(ClassDef c) {
525525
}
526526
loc.addError("Non-abstract class " + c.getName() + " cannot have abstract functions like " + f.getName());
527527
} else if (link instanceof FuncLink) {
528-
toImplement.append("\n ");
529-
toImplement.append(((FuncLink) link).printFunctionTemplate());
528+
FuncLink abstractLink = (FuncLink) link;
529+
if (!hasImplementationInHierarchy(c, abstractLink)) {
530+
toImplement.append("\n ");
531+
toImplement.append(abstractLink.printFunctionTemplate());
532+
}
530533
}
531534
}
532535
}
@@ -536,6 +539,66 @@ private void checkAbstractMethods(ClassDef c) {
536539
}
537540
}
538541

542+
private boolean hasImplementationInHierarchy(ClassDef c, FuncLink abstractFunc) {
543+
return findImplementationLink(c, abstractFunc) != null;
544+
}
545+
546+
private @Nullable FuncLink findImplementationLink(ClassDef c, FuncLink abstractFunc) {
547+
FuncLink best = null;
548+
int bestDistance = Integer.MAX_VALUE;
549+
for (NameLink nameLink : c.attrNameLinks().get(abstractFunc.getName())) {
550+
if (!(nameLink instanceof FuncLink)) {
551+
continue;
552+
}
553+
FuncLink candidate = (FuncLink) nameLink;
554+
if (!WurstValidator.canOverride(candidate, abstractFunc, false)) {
555+
continue;
556+
}
557+
FunctionDefinition candidateDef = candidate.getDef();
558+
if (!(candidateDef instanceof FuncDef)) {
559+
continue;
560+
}
561+
if (candidateDef.attrIsAbstract()) {
562+
continue;
563+
}
564+
ClassDef owner = candidateDef.attrNearestClassDef();
565+
if (owner == null) {
566+
continue;
567+
}
568+
int distance = distanceToOwner(c, owner);
569+
if (distance == Integer.MAX_VALUE) {
570+
continue;
571+
}
572+
if (best == null || distance < bestDistance) {
573+
best = candidate;
574+
bestDistance = distance;
575+
}
576+
}
577+
return best;
578+
}
579+
580+
private int distanceToOwner(ClassDef start, ClassDef owner) {
581+
int distance = 0;
582+
ClassDef current = start;
583+
Set<ClassDef> visited = new HashSet<>();
584+
while (current != null && visited.add(current)) {
585+
if (current == owner) {
586+
return distance;
587+
}
588+
WurstTypeClass currentType = current.attrTypC();
589+
if (currentType == null) {
590+
break;
591+
}
592+
WurstTypeClass superType = currentType.extendedClass();
593+
if (superType == null) {
594+
break;
595+
}
596+
current = superType.getClassDef();
597+
distance++;
598+
}
599+
return Integer.MAX_VALUE;
600+
}
601+
539602
private void visit(StmtExitwhen exitwhen) {
540603
Element parent = exitwhen.getParent();
541604
while (!(parent instanceof FunctionDefinition)) {

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,26 @@ public void implGap() { // #676
537537
);
538538
}
539539

540+
@Test
541+
public void interfaceImplementationFromSuperClass() {
542+
testAssertOkLines(true,
543+
"package test",
544+
" native testSuccess()",
545+
" interface Foo",
546+
" function doSomething()",
547+
" interface Bar",
548+
" function doSomething()",
549+
" class BaseFoo implements Foo",
550+
" override function doSomething()",
551+
" testSuccess()",
552+
" class FooBar extends BaseFoo implements Bar",
553+
" init",
554+
" FooBar fb = new FooBar()",
555+
" fb.doSomething()",
556+
"endpackage"
557+
);
558+
}
559+
540560
@Test
541561
public void testEmptyImplements() {
542562
CompilationResult res = test().executeProg(false)

0 commit comments

Comments
 (0)