Skip to content

Commit 6b07c21

Browse files
committed
Move type checking utils into nf-lang
Signed-off-by: Ben Sherman <bentshermann@gmail.com>
1 parent c16be5c commit 6b07c21

File tree

9 files changed

+36
-297
lines changed

9 files changed

+36
-297
lines changed

src/main/java/nextflow/lsp/ast/ASTNodeStringUtils.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import nextflow.script.dsl.ProcessDsl;
3535
import nextflow.script.formatter.FormattingOptions;
3636
import nextflow.script.formatter.Formatter;
37+
import nextflow.script.types.TypeChecker;
3738
import nextflow.script.types.Types;
3839
import org.codehaus.groovy.ast.AnnotatedNode;
3940
import org.codehaus.groovy.ast.ASTNode;
@@ -250,12 +251,12 @@ public static String parametersToLabel(Parameter[] params) {
250251
private static String variableToLabel(Variable variable) {
251252
var builder = new StringBuilder();
252253
builder.append(variable.getName());
253-
var type = !ClassHelper.isDynamicTyped(variable.getOriginType())
254-
? variable.getOriginType()
255-
: variable.getType();
254+
var type = variable instanceof ASTNode node
255+
? TypeChecker.getType(node)
256+
: variable.getOriginType();
256257
if( type.isArray() )
257258
builder.append("...");
258-
if( !ClassHelper.OBJECT_TYPE.equals(type) ) {
259+
if( !ClassHelper.isObjectType(type) ) {
259260
builder.append(": ");
260261
builder.append(Types.getName(type));
261262
}

src/main/java/nextflow/lsp/ast/LanguageServerASTUtils.java

Lines changed: 6 additions & 273 deletions
Original file line numberDiff line numberDiff line change
@@ -15,40 +15,22 @@
1515
*/
1616
package nextflow.lsp.ast;
1717

18-
import java.lang.reflect.Modifier;
1918
import java.util.Collections;
2019
import java.util.Iterator;
21-
import java.util.List;
22-
import java.util.Optional;
2320

24-
import nextflow.script.ast.ASTNodeMarker;
25-
import nextflow.script.ast.AssignmentExpression;
2621
import nextflow.script.ast.FeatureFlagNode;
2722
import nextflow.script.ast.IncludeModuleNode;
28-
import nextflow.script.ast.ProcessNode;
29-
import nextflow.script.ast.WorkflowNode;
30-
import nextflow.script.dsl.Constant;
31-
import nextflow.script.dsl.Description;
32-
import nextflow.script.types.Channel;
33-
import nextflow.script.types.NamedTuple;
23+
import nextflow.script.types.TypeChecker;
3424
import nextflow.script.types.Types;
3525
import org.codehaus.groovy.ast.ASTNode;
36-
import org.codehaus.groovy.ast.ClassHelper;
3726
import org.codehaus.groovy.ast.ClassNode;
38-
import org.codehaus.groovy.ast.FieldNode;
3927
import org.codehaus.groovy.ast.MethodNode;
40-
import org.codehaus.groovy.ast.Parameter;
4128
import org.codehaus.groovy.ast.Variable;
42-
import org.codehaus.groovy.ast.expr.ArgumentListExpression;
4329
import org.codehaus.groovy.ast.expr.ClassExpression;
4430
import org.codehaus.groovy.ast.expr.ConstructorCallExpression;
45-
import org.codehaus.groovy.ast.expr.DeclarationExpression;
46-
import org.codehaus.groovy.ast.expr.Expression;
47-
import org.codehaus.groovy.ast.expr.MethodCall;
4831
import org.codehaus.groovy.ast.expr.MethodCallExpression;
4932
import org.codehaus.groovy.ast.expr.PropertyExpression;
5033
import org.codehaus.groovy.ast.expr.VariableExpression;
51-
import org.codehaus.groovy.ast.stmt.ExpressionStatement;
5234

5335
import static nextflow.script.ast.ASTUtils.*;
5436

@@ -64,17 +46,16 @@ public class LanguageServerASTUtils {
6446
* class, method, or variable.
6547
*
6648
* @param node
67-
* @param ast
6849
*/
69-
public static ASTNode getDefinition(ASTNode node, ASTNodeCache ast) {
50+
public static ASTNode getDefinition(ASTNode node) {
7051
if( node instanceof VariableExpression ve )
7152
return getDefinitionFromVariable(ve.getAccessedVariable());
7253

7354
if( node instanceof MethodCallExpression mce )
74-
return getMethodFromCallExpression(mce, ast);
55+
return TypeChecker.inferMethodTarget(mce);
7556

7657
if( node instanceof PropertyExpression pe )
77-
return getFieldFromExpression(pe, ast);
58+
return TypeChecker.inferPropertyTarget(pe);
7859

7960
if( node instanceof ClassExpression ce )
8061
return ce.getType().redirect();
@@ -119,7 +100,7 @@ private static ASTNode getDefinitionFromVariable(Variable variable) {
119100
* @param includeDeclaration
120101
*/
121102
public static Iterator<ASTNode> getReferences(ASTNode node, ASTNodeCache ast, boolean includeDeclaration) {
122-
var defNode = getDefinition(node, ast);
103+
var defNode = getDefinition(node);
123104
if( defNode == null )
124105
return Collections.emptyIterator();
125106
return ast.getNodes().stream()
@@ -128,257 +109,9 @@ public static Iterator<ASTNode> getReferences(ASTNode node, ASTNodeCache ast, bo
128109
return false;
129110
if( defNode == otherNode )
130111
return includeDeclaration;
131-
return defNode == getDefinition(otherNode, ast);
112+
return defNode == getDefinition(otherNode);
132113
})
133114
.iterator();
134115
}
135116

136-
/**
137-
* Get the type (i.e. class node) of an ast node.
138-
*
139-
* @param node
140-
* @param ast
141-
*/
142-
public static ClassNode getType(ASTNode node, ASTNodeCache ast) {
143-
var inferredType = inferredType(node, ast);
144-
return Types.SHIM_TYPES.containsKey(inferredType)
145-
? Types.SHIM_TYPES.get(inferredType)
146-
: inferredType;
147-
}
148-
149-
private static ClassNode inferredType(ASTNode node, ASTNodeCache ast) {
150-
if( node instanceof ClassExpression ce ) {
151-
return ce.getType();
152-
}
153-
154-
if( node instanceof ConstructorCallExpression cce ) {
155-
return cce.getType();
156-
}
157-
158-
if( node instanceof MethodCallExpression mce ) {
159-
var mn = getMethodFromCallExpression(mce, ast);
160-
return mn != null
161-
? mn.getReturnType()
162-
: mce.getType();
163-
}
164-
165-
if( node instanceof PropertyExpression pe ) {
166-
var mn = asMethodOutput(pe);
167-
if( mn instanceof ProcessNode pn )
168-
return asProcessOut(pn);
169-
if( mn instanceof WorkflowNode wn )
170-
return asWorkflowOut(wn);
171-
var fn = getFieldFromExpression(pe, ast);
172-
return fn != null
173-
? getType(fn, ast)
174-
: pe.getType();
175-
}
176-
177-
if( node instanceof Variable variable ) {
178-
if( variable.isDynamicTyped() ) {
179-
var defNode = getDefinition(node, ast);
180-
if( defNode instanceof Variable defVar ) {
181-
if( defVar.hasInitialExpression() ) {
182-
return getType(defVar.getInitialExpression(), ast);
183-
}
184-
var declNode = ast.getParent(defNode);
185-
if( declNode instanceof DeclarationExpression de )
186-
return getType(de.getRightExpression(), ast);
187-
}
188-
}
189-
190-
if( variable.getOriginType() != null )
191-
return variable.getOriginType();
192-
}
193-
194-
return node instanceof Expression exp
195-
? exp.getType()
196-
: null;
197-
}
198-
199-
private static MethodNode asMethodOutput(PropertyExpression node) {
200-
if( node.getObjectExpression() instanceof VariableExpression ve && "out".equals(node.getPropertyAsString()) )
201-
return asMethodVariable(ve.getAccessedVariable());
202-
return null;
203-
}
204-
205-
private static ClassNode asProcessOut(ProcessNode pn) {
206-
var cn = new ClassNode(NamedTuple.class);
207-
asDirectives(pn.outputs)
208-
.map(call -> getProcessEmitName(call))
209-
.filter(name -> name != null)
210-
.forEach((name) -> {
211-
var type = ClassHelper.makeCached(Channel.class);
212-
var fn = new FieldNode(name, Modifier.PUBLIC, type, cn, null);
213-
fn.setDeclaringClass(cn);
214-
cn.addField(fn);
215-
});
216-
return cn;
217-
}
218-
219-
private static String getProcessEmitName(MethodCallExpression output) {
220-
return Optional.of(output)
221-
.flatMap(call -> Optional.ofNullable(asNamedArgs(call)))
222-
.flatMap(namedArgs ->
223-
namedArgs.stream()
224-
.filter(entry -> "emit".equals(entry.getKeyExpression().getText()))
225-
.findFirst()
226-
)
227-
.flatMap(entry -> Optional.ofNullable(
228-
entry.getValueExpression() instanceof VariableExpression ve ? ve.getName() : null
229-
))
230-
.orElse(null);
231-
}
232-
233-
private static ClassNode asWorkflowOut(WorkflowNode wn) {
234-
var cn = new ClassNode(NamedTuple.class);
235-
asBlockStatements(wn.emits).stream()
236-
.map(stmt -> ((ExpressionStatement) stmt).getExpression())
237-
.map(emit -> getWorkflowEmitName(emit))
238-
.filter(name -> name != null)
239-
.forEach((name) -> {
240-
var type = ClassHelper.dynamicType();
241-
var fn = new FieldNode(name, Modifier.PUBLIC, type, cn, null);
242-
fn.setDeclaringClass(cn);
243-
cn.addField(fn);
244-
});
245-
return cn;
246-
}
247-
248-
private static String getWorkflowEmitName(Expression emit) {
249-
if( emit instanceof VariableExpression ve ) {
250-
return ve.getName();
251-
}
252-
else if( emit instanceof AssignmentExpression ae ) {
253-
var left = (VariableExpression)ae.getLeftExpression();
254-
return left.getName();
255-
}
256-
return null;
257-
}
258-
259-
private static FieldNode getFieldFromExpression(PropertyExpression node, ASTNodeCache ast) {
260-
var cn = getType(node.getObjectExpression(), ast);
261-
if( cn == null )
262-
return null;
263-
var name = node.getPropertyAsString();
264-
var result = cn.getField(name);
265-
if( result != null )
266-
return result;
267-
return cn.getMethods().stream()
268-
.filter((mn) -> {
269-
if( !mn.isPublic() )
270-
return false;
271-
var an = findAnnotation(mn, Constant.class);
272-
if( !an.isPresent() )
273-
return false;
274-
return name.equals(an.get().getMember("value").getText());
275-
})
276-
.map((mn) -> {
277-
var fn = new FieldNode(name, 0xF, mn.getReturnType(), mn.getDeclaringClass(), null);
278-
findAnnotation(mn, Description.class).ifPresent((an) -> {
279-
fn.addAnnotation(an);
280-
});
281-
return fn;
282-
})
283-
.findFirst().orElse(null);
284-
}
285-
286-
/**
287-
* Find the method node which most closely matches a call expression.
288-
*
289-
* @param node
290-
* @param ast
291-
* @param argIndex
292-
*/
293-
public static MethodNode getMethodFromCallExpression(MethodCall node, ASTNodeCache ast, int argIndex) {
294-
var methods = getMethodOverloadsFromCallExpression(node, ast);
295-
var arguments = (ArgumentListExpression) node.getArguments();
296-
return methods.stream()
297-
.max((MethodNode m1, MethodNode m2) -> {
298-
var score1 = getArgumentsScore(m1.getParameters(), arguments, argIndex);
299-
var score2 = getArgumentsScore(m2.getParameters(), arguments, argIndex);
300-
return Integer.compare(score1, score2);
301-
})
302-
.orElse(null);
303-
}
304-
305-
public static MethodNode getMethodFromCallExpression(MethodCall node, ASTNodeCache ast) {
306-
return getMethodFromCallExpression(node, ast, -1);
307-
}
308-
309-
/**
310-
* Get the list of available method overloads for a method call expression.
311-
*
312-
* @param node
313-
* @param ast
314-
*/
315-
public static List<MethodNode> getMethodOverloadsFromCallExpression(MethodCall node, ASTNodeCache ast) {
316-
if( node instanceof MethodCallExpression mce ) {
317-
if( mce.isImplicitThis() ) {
318-
var target = (MethodNode) mce.getNodeMetaData(ASTNodeMarker.METHOD_TARGET);
319-
if( target != null )
320-
return List.of(target);
321-
}
322-
else {
323-
var leftType = getType(mce.getObjectExpression(), ast);
324-
if( leftType != null )
325-
return methodsForType(leftType, mce.getMethodAsString());
326-
}
327-
}
328-
329-
if( node instanceof ConstructorCallExpression cce ) {
330-
var constructorType = cce.getType();
331-
if( constructorType != null ) {
332-
return constructorType.getDeclaredConstructors().stream()
333-
.map(ctor -> (MethodNode) ctor)
334-
.toList();
335-
}
336-
}
337-
338-
return Collections.emptyList();
339-
}
340-
341-
private static List<MethodNode> methodsForType(ClassNode cn, String name) {
342-
try {
343-
return cn.getAllDeclaredMethods().stream()
344-
.filter(mn -> mn.getName().equals(name))
345-
.filter(mn -> !ClassHelper.isObjectType(mn.getDeclaringClass()))
346-
.toList();
347-
}
348-
catch( NullPointerException e ) {
349-
return Collections.emptyList();
350-
}
351-
}
352-
353-
private static int getArgumentsScore(Parameter[] parameters, ArgumentListExpression arguments, int argIndex) {
354-
var paramsCount = parameters.length;
355-
var expressionsCount = arguments.getExpressions().size();
356-
var argsCount = argIndex >= expressionsCount
357-
? argIndex + 1
358-
: expressionsCount;
359-
var minCount = Math.min(paramsCount, argsCount);
360-
361-
int score = 0;
362-
if( minCount == 0 && paramsCount == argsCount )
363-
score++;
364-
365-
for( int i = 0; i < minCount; i++ ) {
366-
var paramType = (i < paramsCount) ? parameters[i].getType() : null;
367-
var argType = (i < expressionsCount) ? arguments.getExpression(i).getType() : null;
368-
if( argType != null && paramType != null ) {
369-
// equal types > subtypes > different types
370-
if( argType.equals(paramType) )
371-
score += 1000;
372-
else if( argType.isDerivedFrom(paramType) )
373-
score += 100;
374-
else
375-
score++;
376-
}
377-
else if( paramType != null ) {
378-
score++;
379-
}
380-
}
381-
return score;
382-
}
383-
384117
}

0 commit comments

Comments
 (0)