Skip to content

Commit 18837b0

Browse files
finalize subtyping checks
1 parent c543fb7 commit 18837b0

File tree

5 files changed

+91
-22
lines changed

5 files changed

+91
-22
lines changed

src/main/java/io/github/bldl/annotationProcessing/VarianceType.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@
33
public enum VarianceType {
44
INVARIANT,
55
COVARIANT,
6-
CONTRAVARIANT
6+
CONTRAVARIANT,
7+
SIDE
78
}

src/main/java/io/github/bldl/astParsing/AstManipulator.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,8 @@ private void changeAST(File dir, ClassData classData, Map<String, MethodData> me
136136
Set<Pair<String, ClassOrInterfaceType>> varsToWatch = new HashSet<>();
137137
cu.accept(new VariableCollector(classData), varsToWatch);
138138
cu.accept(
139-
new SubtypingCheckVisitor(collectMethodParams(cu, classData), messager, varsToWatch, classData,
139+
new SubtypingCheckVisitor(collectMethodParams(cu, classData), collectMethodTypes(cu), messager,
140+
varsToWatch, classData,
140141
classHierarchy),
141142
null);
142143
cu.accept(new TypeEraserVisitor(classData), null);
@@ -209,4 +210,13 @@ private Map<String, Map<Integer, Type>> collectMethodParams(CompilationUnit cu,
209210
});
210211
return mp;
211212
}
213+
214+
private Map<String, Type> collectMethodTypes(CompilationUnit cu) {
215+
Map<String, Type> mp = new HashMap<>();
216+
cu.findAll(MethodDeclaration.class).forEach(dec -> {
217+
String methodName = dec.getNameAsString();
218+
mp.put(methodName, dec.getType());
219+
});
220+
return mp;
221+
}
212222
}

src/main/java/io/github/bldl/astParsing/visitors/SubtypingCheckVisitor.java

Lines changed: 70 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import java.util.Map.Entry;
66
import java.util.Optional;
77
import java.util.Set;
8-
98
import javax.annotation.processing.Messager;
109
import javax.tools.Diagnostic.Kind;
1110

@@ -27,6 +26,7 @@
2726
import com.github.javaparser.ast.type.Type;
2827
import com.github.javaparser.ast.visitor.VoidVisitorAdapter;
2928

29+
import io.github.bldl.annotationProcessing.annotations.MyVariance;
3030
import io.github.bldl.astParsing.util.ClassData;
3131
import io.github.bldl.astParsing.util.ParamData;
3232
import io.github.bldl.graph.ClassHierarchyGraph;
@@ -40,10 +40,12 @@ public class SubtypingCheckVisitor extends VoidVisitorAdapter<Void> {
4040
private final ClassData classData;
4141
private final ClassHierarchyGraph<String> classHierarchy;
4242

43-
public SubtypingCheckVisitor(Map<String, Map<Integer, Type>> methodParams, Messager messager,
43+
public SubtypingCheckVisitor(Map<String, Map<Integer, Type>> methodParams, Map<String, Type> methodTypes,
44+
Messager messager,
4445
Set<Pair<String, ClassOrInterfaceType>> varsToWatch, ClassData classData,
4546
ClassHierarchyGraph<String> classHierarchy) {
4647
this.methodParams = methodParams;
48+
this.methodTypes = methodTypes;
4749
this.messager = messager;
4850
this.classData = classData;
4951
this.classHierarchy = classHierarchy;
@@ -77,6 +79,16 @@ public void visit(AssignExpr assignExpr, Void arg) {
7779
super.visit(assignExpr, arg);
7880
ClassOrInterfaceType assignedType = resolveType(assignExpr.getValue()),
7981
assigneeType = resolveType(assignExpr.getTarget());
82+
if (assignedType == null || assigneeType == null) {
83+
messager.printMessage(Kind.WARNING, "Cannot resolve type for expression: " + assignExpr.toString());
84+
return;
85+
}
86+
boolean valid = isValidSubtype(assigneeType, assignedType, null);
87+
if (!valid)
88+
messager.printMessage(Kind.ERROR,
89+
String.format("Invalid subtype for assignment expression call: %s\n%s is not a subtype of %s",
90+
assignExpr.toString(),
91+
assignedType.toString(), assigneeType.toString()));
8092
}
8193

8294
public void visit(ForEachStmt n, Void arg) {
@@ -92,12 +104,16 @@ public void visit(VariableDeclarator declaration, Void arg) {
92104
if (initializer.isEmpty())
93105
return;
94106
ClassOrInterfaceType assignedType = resolveType(initializer.get());
107+
if (assignedType == null)
108+
return;
95109
boolean valid = isValidSubtype((ClassOrInterfaceType) assigneeType,
96110
assignedType,
97111
classData.params());
98112
if (!valid)
99113
messager.printMessage(Kind.ERROR,
100-
String.format("Invalid subtype for variable declaration: %s", declaration.toString()));
114+
String.format("Invalid subtype for variable declaration: %s\n %s is not a subtype of %s",
115+
declaration.toString(),
116+
assignedType.toString(), assigneeType.toString()));
101117
}
102118

103119
private ClassOrInterfaceType resolveType(Expression e) {
@@ -147,14 +163,56 @@ private boolean isValidSubtype(ClassOrInterfaceType assigneeType, ClassOrInterfa
147163
String.format("%s is not a user defined type, so no subtyping checks can be made", assignedType));
148164
return true;
149165
}
150-
return true;
151-
// switch (annotation.variance()) {
152-
// case COVARIANT:
153-
// return classHierarchy.isDescendant(assignedType, assigneeType);
154-
// case CONTRAVARIANT:
155-
// return classHierarchy.isDescendant(assigneeType, assignedType);
156-
// default:
157-
// return false;
158-
// }
166+
if (!assigneeType.getTypeArguments().isPresent() || !assignedType.getTypeArguments().isPresent())
167+
return true;
168+
169+
var assigneeArgs = assigneeType.getTypeArguments().get();
170+
var assignedArgs = assignedType.getTypeArguments().get();
171+
boolean isSubtype = true;
172+
173+
if (assignedArgs.size() == 0)
174+
return true; // cannot perform type inference
175+
176+
if (assigneeType.getNameAsString().equals(classData.className())) {
177+
Map<Integer, MyVariance> mp = new HashMap<>();
178+
for (var param : classData.params().values())
179+
mp.put(param.index(), param.variance());
180+
for (int i = 0; i < assigneeArgs.size(); ++i) {
181+
if (!(assignedArgs.get(i) instanceof ClassOrInterfaceType)
182+
|| !(assignedArgs.get(i) instanceof ClassOrInterfaceType)) {
183+
continue;
184+
}
185+
if (!mp.containsKey(i)) {
186+
isSubtype = isSubtype && isValidSubtype((ClassOrInterfaceType) assigneeArgs.get(i),
187+
(ClassOrInterfaceType) assignedArgs.get(i),
188+
params);
189+
continue;
190+
}
191+
switch (mp.get(i).variance()) {
192+
case COVARIANT:
193+
return classHierarchy.isDescendant(
194+
((ClassOrInterfaceType) assigneeArgs.get(i)).getNameAsString(),
195+
((ClassOrInterfaceType) assignedArgs.get(i)).getNameAsString(), mp.get(i).depth());
196+
case CONTRAVARIANT:
197+
return classHierarchy.isDescendant(
198+
((ClassOrInterfaceType) assignedArgs.get(i)).getNameAsString(),
199+
((ClassOrInterfaceType) assigneeArgs.get(i)).getNameAsString(), mp.get(i).depth());
200+
default:
201+
return false;
202+
}
203+
}
204+
}
205+
206+
for (int i = 0; i < assigneeArgs.size(); ++i) {
207+
if (!(assignedArgs.get(i) instanceof ClassOrInterfaceType)
208+
|| !(assignedArgs.get(i) instanceof ClassOrInterfaceType)) {
209+
continue;
210+
}
211+
isSubtype = isSubtype && isValidSubtype((ClassOrInterfaceType) assigneeArgs.get(i),
212+
(ClassOrInterfaceType) assignedArgs.get(i),
213+
params);
214+
}
215+
return classHierarchy.isDescendant(assigneeType.getNameAsString(),
216+
assignedType.getNameAsString(), -1);
159217
}
160218
}

src/main/java/io/github/bldl/graph/ClassHierarchyGraph.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public boolean addEdge(T initial, T terminal) {
2929
if (!containsVertex(initial) || !containsVertex(terminal))
3030
return false;
3131
Set<T> visited = new HashSet<>();
32-
dfs(terminal, visited);
32+
dfs(terminal, visited, -1, 0);
3333
if (visited.contains(initial)) {
3434
throw new IllegalArgumentException(
3535
String.format("Adding an edge between %s and %s will create a cycle", initial,
@@ -67,18 +67,18 @@ public Iterable<T> getVertices() {
6767
* @param descendant the presumed descendant
6868
* @return whether {@code descendant} is a descendant of {@code ancestor}
6969
*/
70-
public boolean isDescendant(T ancestor, T descendant) {
70+
public boolean isDescendant(T ancestor, T descendant, int max_depth) {
7171
Set<T> visited = new HashSet<>();
72-
dfs(ancestor, visited);
72+
dfs(ancestor, visited, max_depth, 0);
7373
return visited.contains(descendant);
7474
}
7575

76-
private void dfs(T current, Set<T> visited) {
77-
if (visited.contains(current))
76+
private void dfs(T current, Set<T> visited, int max_depth, int curr_depth) {
77+
if (visited.contains(current) || curr_depth >= max_depth && max_depth >= 0)
7878
return;
7979
visited.add(current);
8080
for (T vertex : getOutVertices(current)) {
81-
dfs(vertex, visited);
81+
dfs(vertex, visited, max_depth, curr_depth + 1);
8282
}
8383
}
8484

src/test/java/anthonisen/felix/graph/TestClassHierarchyGraph.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ public void testIsDescendant() {
7878
for (int i = 0; i < 4; ++i) {
7979
graph.addEdge(i, i + 1);
8080
}
81-
assertTrue(graph.isDescendant(0, 4));
82-
assertFalse(graph.isDescendant(4, 0));
81+
assertTrue(graph.isDescendant(0, 4, -1));
82+
assertFalse(graph.isDescendant(4, 0, -1));
8383
}
8484
}

0 commit comments

Comments
 (0)