Skip to content

Commit 6b68817

Browse files
committed
Preserve named arguments in process outputs when converting to static types
1 parent f78ff0d commit 6b68817

2 files changed

Lines changed: 62 additions & 46 deletions

File tree

src/main/java/nextflow/lsp/services/script/ProcessConverter.java

Lines changed: 53 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,16 @@ private Parameter[] typedInputs(Statement inputs, List<Statement> stagers) {
103103
.toArray(Parameter[]::new);
104104
}
105105

106+
/**
107+
* Convert a legacy input to a typed input.
108+
*
109+
* Stage directives are added as needed to handle
110+
* inputs that require custom staging, such as env
111+
* and stdin.
112+
*
113+
* @param call
114+
* @param stagers
115+
*/
106116
private Parameter typedInput(MethodCallExpression call, List<Statement> stagers) {
107117
var qualifier = call.getMethodAsString();
108118

@@ -257,12 +267,20 @@ private void checkExpressionOutput(List<Statement> statements) {
257267

258268
private static final Token RIGHT_SHIFT = Token.newSymbol(Types.RIGHT_SHIFT, -1, -1);
259269

270+
/**
271+
* Convert a legacy output to a typed output.
272+
*
273+
* Topic emissions are added as needed for outputs
274+
* that specify a topic.
275+
*
276+
* @param call
277+
* @param topics
278+
*/
260279
private Statement typedOutput(MethodCallExpression call, List<Statement> topics) {
261280
var namedArgs = namedArgs(call);
262281
var hasEmit = namedArgs.containsKey("emit");
263282
var hasTopic = namedArgs.containsKey("topic");
264-
var optional = namedArgs.get("optional") instanceof ConstantExpression ce && Boolean.TRUE.equals(ce.getValue());
265-
var outputValue = typedOutputValue(call, optional);
283+
var outputValue = typedOutputValue(call);
266284
if( hasTopic ) {
267285
var topicName = namedArgs.get("topic").getText();
268286
var topic = stmt(binX(outputValue, RIGHT_SHIFT, stringX(topicName)));
@@ -276,6 +294,12 @@ private Statement typedOutput(MethodCallExpression call, List<Statement> topics)
276294
return stmt(new AssignmentExpression(varX(outputName), outputValue));
277295
}
278296

297+
/**
298+
* Get the named arguments of a method call as a
299+
* mapping of names to expressions.
300+
*
301+
* @param call
302+
*/
279303
private static Map<String,Expression> namedArgs(MethodCallExpression call) {
280304
var entries = asNamedArgs(call).stream()
281305
.map((entry) -> {
@@ -287,19 +311,8 @@ private static Map<String,Expression> namedArgs(MethodCallExpression call) {
287311
return Map.ofEntries(entries);
288312
}
289313

290-
private static Expression typedOutputValue(MethodCallExpression call, boolean optional) {
291-
// TODO: preserve named args in output dsl xform, filter out emit/topic/optional after xform
292-
var arguments = withoutNamedArgs(call);
293-
var result = callThisX(call.getMethodAsString(), args(arguments));
294-
result.setSourcePosition(call);
295-
return new OutputDslTransformer(optional).transform(result);
296-
}
297-
298-
private static List<Expression> withoutNamedArgs(MethodCallExpression call) {
299-
var args = asMethodCallArguments(call);
300-
return args.size() > 0 && args.get(0) instanceof NamedArgumentListExpression
301-
? args.subList(1, args.size())
302-
: args;
314+
private static Expression typedOutputValue(MethodCallExpression call) {
315+
return new OutputDslTransformer().transform(call);
303316
}
304317

305318
private static Expression stringX(String str) {
@@ -391,17 +404,16 @@ private static ClassNode inputType(String type) {
391404

392405
class OutputDslTransformer implements ExpressionTransformer {
393406

394-
private boolean optional;
395-
396-
public OutputDslTransformer(boolean optional) {
397-
this.optional = optional;
398-
}
407+
private static final List<String> LEGACY_OPTS = List.of("arity", "emit", "topic");
399408

400409
@Override
401410
public Expression transform(Expression node) {
402411
if( node == null )
403412
return null;
404413

414+
if( node instanceof ArgumentListExpression ale )
415+
return transformArgumentListExpression(ale);
416+
405417
if( node instanceof VariableExpression ve )
406418
return transformVariableExpression(ve);
407419

@@ -411,6 +423,24 @@ public Expression transform(Expression node) {
411423
return node.transformExpression(this);
412424
}
413425

426+
private Expression transformArgumentListExpression(ArgumentListExpression ale) {
427+
var args = ale.getExpressions();
428+
if( args.size() > 0 && args.get(0) instanceof NamedArgumentListExpression nale ) {
429+
var entries = nale.getMapEntryExpressions().stream()
430+
.filter((e) -> (
431+
!LEGACY_OPTS.contains(e.getKeyExpression().getText())
432+
))
433+
.toList();
434+
var newArgs = new ArrayList<Expression>(args.size());
435+
if( !entries.isEmpty() )
436+
newArgs.add(new NamedArgumentListExpression(entries));
437+
for( var arg : args.subList(1, args.size()) )
438+
newArgs.add(transform(arg));
439+
return new ArgumentListExpression(newArgs);
440+
}
441+
return ale.transformExpression(this);
442+
}
443+
414444
private Expression transformVariableExpression(VariableExpression ve) {
415445
if( "stdout".equals(ve) )
416446
return callThisX(ve.getName(), new ArgumentListExpression());
@@ -419,17 +449,15 @@ private Expression transformVariableExpression(VariableExpression ve) {
419449

420450
private Expression transformMethodCallExpression(MethodCallExpression mce) {
421451
if( !mce.isImplicitThis() )
422-
return mce.transformExpression(this);
452+
return mce;
423453
var name = mce.getMethodAsString();
424454
if( "val".equals(name) )
425455
return lastArg(mce);
426-
var arguments = transform(mce.getArguments());
456+
var arguments = mce.getArguments();
427457
if( "file".equals(name) || "path".equals(name) ) {
428458
name = isFileCollection(arguments) ? "files" : "file";
429-
if( optional )
430-
addNamedArg(arguments, entryX(constX("optional"), constX(true)));
431459
}
432-
var result = callThisX(name, arguments);
460+
var result = callThisX(name, transform(arguments));
433461
result.setSourcePosition(mce);
434462
return result;
435463
}
@@ -444,18 +472,6 @@ public static boolean isFileCollection(Expression arguments) {
444472
return false;
445473
}
446474

447-
private static void addNamedArg(Expression arguments, MapEntryExpression entry) {
448-
var args = ((TupleExpression) arguments).getExpressions();
449-
if( args.size() > 0 && args.get(0) instanceof NamedArgumentListExpression nale ) {
450-
nale.addMapEntryExpression(entry);
451-
}
452-
else {
453-
var namedArgs = new NamedArgumentListExpression();
454-
namedArgs.addMapEntryExpression(entry);
455-
args.add(0, namedArgs);
456-
}
457-
}
458-
459475
private static Expression lastArg(MethodCallExpression call) {
460476
var args = asMethodCallArguments(call);
461477
return args.get(args.size() - 1);

src/test/groovy/nextflow/lsp/services/script/ConvertScriptStaticTypesTest.groovy

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -191,12 +191,11 @@ class ConvertScriptStaticTypesTest extends Specification {
191191
"path 'output.txt'" | "file('output.txt')" | null
192192
"path 'output.txt', emit: txt" | "txt = file('output.txt')" | null
193193
"path 'output.txt', topic: txt" | null | "file('output.txt') >> 'txt'"
194-
// "path 'output.txt', hidden: true" | "file('output.txt', hidden: true)" | null
194+
"path 'output.txt', hidden: true" | "file('output.txt', hidden: true)" | null
195195
"path 'output.txt', optional: true" | "file('output.txt', optional: true)" | null
196196
"path '*.txt', arity: '1'" | "file('*.txt')" | null
197-
// "path '*.txt', arity: '1..*'" | "files('*.txt')" | null
198-
// "path '*.txt', arity: '0..*'" | "files('*.txt', optional: true)" | null
199-
// "path '-'" | "stdout()" | null
197+
"path '*.txt', arity: '1..*'" | "files('*.txt')" | null
198+
"path '*.txt', arity: '0..*'" | "files('*.txt')" | null
200199
}
201200

202201
def 'should convert env outputs' () {
@@ -231,11 +230,12 @@ class ConvertScriptStaticTypesTest extends Specification {
231230
checkOutputs(service, OUTPUT, TYPED_OUTPUT)
232231

233232
where:
234-
OUTPUT | TYPED_OUTPUT
235-
"tuple val('id'), path('*.fastq')" | "tuple('id', file('*.fastq'))"
236-
"tuple stdout(), val('id')" | "tuple(stdout(), 'id')"
237-
"tuple val('x'), val('y'), emit: xy" | "xy = tuple('x', 'y')"
238-
"tuple stdout(), env('BAR'), emit: bar" | "bar = tuple(stdout(), env('BAR'))"
233+
OUTPUT | TYPED_OUTPUT
234+
"tuple val('id'), path('*.fastq')" | "tuple('id', file('*.fastq'))"
235+
"tuple stdout(), val('id')" | "tuple(stdout(), 'id')"
236+
"tuple val('x'), val('y'), emit: xy" | "xy = tuple('x', 'y')"
237+
"tuple stdout(), env('BAR'), emit: bar" | "bar = tuple(stdout(), env('BAR'))"
238+
"tuple val('id'), path('*.fastq', hidden: true), emit: bar" | "bar = tuple('id', file('*.fastq', hidden: true))"
239239
}
240240

241241
}

0 commit comments

Comments
 (0)