|
1 | 1 | package com.garciat.typeclasses.processor; |
2 | 2 |
|
| 3 | +import static com.garciat.typeclasses.types.Unit.unit; |
| 4 | + |
3 | 5 | import com.garciat.typeclasses.TypeClasses; |
4 | 6 | import com.garciat.typeclasses.api.Ty; |
5 | | -import com.garciat.typeclasses.types.Either; |
6 | | -import com.sun.source.tree.*; |
7 | | -import com.sun.source.util.*; |
| 7 | +import com.garciat.typeclasses.types.Maybe; |
| 8 | +import com.garciat.typeclasses.types.Unit; |
| 9 | +import com.sun.source.tree.ClassTree; |
| 10 | +import com.sun.source.tree.ExpressionTree; |
| 11 | +import com.sun.source.tree.MethodInvocationTree; |
| 12 | +import com.sun.source.tree.NewClassTree; |
| 13 | +import com.sun.source.tree.Tree; |
| 14 | +import com.sun.source.util.JavacTask; |
| 15 | +import com.sun.source.util.Plugin; |
| 16 | +import com.sun.source.util.TaskEvent; |
| 17 | +import com.sun.source.util.TaskListener; |
| 18 | +import com.sun.source.util.TreePath; |
| 19 | +import com.sun.source.util.TreePathScanner; |
| 20 | +import com.sun.source.util.Trees; |
8 | 21 | import java.lang.reflect.Method; |
9 | | -import javax.lang.model.element.Element; |
10 | 22 | import javax.lang.model.element.ExecutableElement; |
11 | 23 | import javax.lang.model.element.TypeElement; |
12 | 24 | import javax.lang.model.type.DeclaredType; |
@@ -60,54 +72,108 @@ private WitnessCallScanner(Trees trees) { |
60 | 72 |
|
61 | 73 | @Override |
62 | 74 | public Void visitMethodInvocation(MethodInvocationTree node, Void arg) { |
63 | | - Element element = trees.getElement(getCurrentPath()); |
64 | | - |
65 | | - if (isMethodCall(WITNESS_METHOD, element)) { |
66 | | - // Found a call to TypeClasses.witness() |
67 | | - // The first argument is expected to be of the form "new Ty<>() {}" |
68 | | - ExpressionTree firstArg = node.getArguments().getFirst(); |
69 | | - |
70 | | - // Check if it's a "new Ty<>() {}" anonymous class creation |
71 | | - if (firstArg instanceof NewClassTree newClass) { |
72 | | - Tree tyApp = newClass.getClassBody().getImplementsClause().getFirst(); |
73 | | - |
74 | | - TypeMirror typeMirror = |
75 | | - trees.getTypeMirror(trees.getPath(getCurrentPath().getCompilationUnit(), tyApp)); |
76 | | - |
77 | | - // Try to extract witness type and verify resolution |
78 | | - if (typeMirror instanceof DeclaredType declaredType) { |
79 | | - TypeMirror witnessTypeMirror = declaredType.getTypeArguments().getFirst(); |
80 | | - |
81 | | - ParsedType target = system.parse(witnessTypeMirror); |
82 | | - |
83 | | - switch (WitnessResolution.resolve(system, target)) { |
84 | | - case Either.Left< |
85 | | - WitnessResolution.ResolutionError, WitnessResolution.InstantiationPlan>( |
86 | | - var error) -> |
87 | | - this.trees.printMessage( |
88 | | - Diagnostic.Kind.ERROR, |
89 | | - "Failed to resolve witness for type: " |
90 | | - + witnessTypeMirror |
91 | | - + "\nReason: " |
92 | | - + error.format(), |
93 | | - getCurrentPath().getLeaf(), |
94 | | - getCurrentPath().getCompilationUnit()); |
95 | | - case Either.Right< |
96 | | - WitnessResolution.ResolutionError, WitnessResolution.InstantiationPlan> |
97 | | - ignore -> {} |
98 | | - } |
99 | | - } |
100 | | - } |
101 | | - } |
| 75 | + Parser.unaryMethodCallArgument(WITNESS_METHOD) |
| 76 | + .flatMap(Parser.newAnonymousClassBody()) |
| 77 | + .flatMap(Parser.singleImplementsClause()) |
| 78 | + .flatMap(Parser.treeTypeMirror()) |
| 79 | + .flatMap(Parser.rawTypeMatches(Ty.class)) |
| 80 | + .flatMap(Parser.unaryTypeArgument()) |
| 81 | + .parse(trees, getCurrentPath(), node) |
| 82 | + .fold( |
| 83 | + Unit::unit, |
| 84 | + witnessType -> |
| 85 | + WitnessResolution.resolve(system, system.parse(witnessType)) |
| 86 | + .fold( |
| 87 | + error -> { |
| 88 | + this.trees.printMessage( |
| 89 | + Diagnostic.Kind.ERROR, |
| 90 | + "Failed to resolve witness for type: " |
| 91 | + + witnessType |
| 92 | + + "\nReason: " |
| 93 | + + error.format(), |
| 94 | + getCurrentPath().getLeaf(), |
| 95 | + getCurrentPath().getCompilationUnit()); |
| 96 | + return unit(); |
| 97 | + }, |
| 98 | + plan -> unit())); |
102 | 99 |
|
103 | 100 | return super.visitMethodInvocation(node, arg); |
104 | 101 | } |
105 | 102 | } |
| 103 | +} |
| 104 | + |
| 105 | +interface Parser<T, R> { |
| 106 | + Maybe<R> parse(Trees trees, TreePath current, T input); |
| 107 | + |
| 108 | + default <S> Parser<T, S> flatMap(Parser<R, S> next) { |
| 109 | + return (trees, current, input) -> |
| 110 | + this.parse(trees, current, input).flatMap(r -> next.parse(trees, current, r)); |
| 111 | + } |
| 112 | + |
| 113 | + static <T> Parser<MethodInvocationTree, ExpressionTree> unaryMethodCallArgument(Method target) { |
| 114 | + return (trees, current, input) -> { |
| 115 | + if (trees.getElement(current) instanceof ExecutableElement method |
| 116 | + && method.getSimpleName().contentEquals(target.getName()) |
| 117 | + && method.getEnclosingElement() instanceof TypeElement methodOwner |
| 118 | + && methodOwner.getQualifiedName().contentEquals(target.getDeclaringClass().getName()) |
| 119 | + && input.getArguments().size() == 1) { |
| 120 | + return Maybe.just(input.getArguments().getFirst()); |
| 121 | + } else { |
| 122 | + return Maybe.nothing(); |
| 123 | + } |
| 124 | + }; |
| 125 | + } |
| 126 | + |
| 127 | + static Parser<ExpressionTree, ClassTree> newAnonymousClassBody() { |
| 128 | + return (trees, current, input) -> { |
| 129 | + if (input instanceof NewClassTree newClass && newClass.getClassBody() != null) { |
| 130 | + return Maybe.just(newClass.getClassBody()); |
| 131 | + } else { |
| 132 | + return Maybe.nothing(); |
| 133 | + } |
| 134 | + }; |
| 135 | + } |
| 136 | + |
| 137 | + static Parser<ClassTree, Tree> singleImplementsClause() { |
| 138 | + return (trees, current, input) -> { |
| 139 | + if (input.getImplementsClause() != null && input.getImplementsClause().size() == 1) { |
| 140 | + return Maybe.just(input.getImplementsClause().getFirst()); |
| 141 | + } else { |
| 142 | + return Maybe.nothing(); |
| 143 | + } |
| 144 | + }; |
| 145 | + } |
| 146 | + |
| 147 | + static Parser<Tree, TypeMirror> treeTypeMirror() { |
| 148 | + return (trees, current, input) -> { |
| 149 | + try { |
| 150 | + TypeMirror typeMirror = |
| 151 | + trees.getTypeMirror(trees.getPath(current.getCompilationUnit(), input)); |
| 152 | + return Maybe.just(typeMirror); |
| 153 | + } catch (IllegalArgumentException e) { |
| 154 | + return Maybe.nothing(); |
| 155 | + } |
| 156 | + }; |
| 157 | + } |
| 158 | + |
| 159 | + static Parser<TypeMirror, DeclaredType> rawTypeMatches(Class<?> cls) { |
| 160 | + return (trees, current, input) -> { |
| 161 | + if (input instanceof DeclaredType declaredType |
| 162 | + && declaredType.asElement() instanceof TypeElement typeElement |
| 163 | + && typeElement.getQualifiedName().contentEquals(cls.getName())) { |
| 164 | + return Maybe.just(declaredType); |
| 165 | + } |
| 166 | + return Maybe.nothing(); |
| 167 | + }; |
| 168 | + } |
106 | 169 |
|
107 | | - private static boolean isMethodCall(Method target, Element element) { |
108 | | - return element instanceof ExecutableElement method |
109 | | - && method.getSimpleName().contentEquals(target.getName()) |
110 | | - && method.getEnclosingElement() instanceof TypeElement methodOwner |
111 | | - && methodOwner.getQualifiedName().contentEquals(target.getDeclaringClass().getName()); |
| 170 | + static Parser<DeclaredType, TypeMirror> unaryTypeArgument() { |
| 171 | + return (trees, current, input) -> { |
| 172 | + if (input.getTypeArguments().size() == 1) { |
| 173 | + return Maybe.just(input.getTypeArguments().getFirst()); |
| 174 | + } else { |
| 175 | + return Maybe.nothing(); |
| 176 | + } |
| 177 | + }; |
112 | 178 | } |
113 | 179 | } |
0 commit comments