Skip to content

Commit 1cf782e

Browse files
committed
Use ConfigOption additionalTypes in config spec validation
1 parent 8c13ab0 commit 1cf782e

File tree

5 files changed

+42
-16
lines changed

5 files changed

+42
-16
lines changed

build.gradle

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ configurations {
3333
}
3434

3535
dependencies {
36-
implementation 'io.nextflow:nf-lang:25.10.0'
36+
implementation 'io.nextflow:nf-lang:25.12.0-edge'
3737
implementation 'org.apache.groovy:groovy:4.0.28'
3838
implementation 'org.apache.groovy:groovy-json:4.0.28'
3939
implementation 'org.apache.groovy:groovy-yaml:4.0.28'
@@ -45,13 +45,13 @@ dependencies {
4545
runtimeOnly 'org.yaml:snakeyaml:2.2'
4646

4747
// include Nextflow runtime at build-time to extract language definitions
48-
nextflowRuntime 'io.nextflow:nextflow:25.10.0'
49-
nextflowRuntime 'io.nextflow:nf-amazon:3.4.1'
50-
nextflowRuntime 'io.nextflow:nf-azure:1.20.2'
51-
nextflowRuntime 'io.nextflow:nf-google:1.23.3'
52-
nextflowRuntime 'io.nextflow:nf-k8s:1.2.2'
53-
nextflowRuntime 'io.nextflow:nf-tower:1.17.1'
54-
nextflowRuntime 'io.nextflow:nf-wave:1.16.1'
48+
nextflowRuntime 'io.nextflow:nextflow:25.12.0-edge'
49+
nextflowRuntime 'io.nextflow:nf-amazon:3.6.0'
50+
nextflowRuntime 'io.nextflow:nf-azure:1.21.0'
51+
nextflowRuntime 'io.nextflow:nf-google:1.25.0'
52+
nextflowRuntime 'io.nextflow:nf-k8s:1.4.0'
53+
nextflowRuntime 'io.nextflow:nf-tower:1.19.0'
54+
nextflowRuntime 'io.nextflow:nf-wave:1.18.0'
5555

5656
testImplementation ('org.objenesis:objenesis:3.4')
5757
testImplementation ('net.bytebuddy:byte-buddy:1.14.17')

src/main/java/nextflow/lsp/services/config/ConfigCompletionProvider.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ private void addConfigOptions(List<String> names, SpecNode.Scope spec) {
183183
return;
184184
scope.children().forEach((name, child) -> {
185185
if( child instanceof SpecNode.Option option )
186-
ch.addItem(configOption(name, option.description(), option.type()));
186+
ch.addItem(configOption(name, option.description(), option.types().get(0)));
187187
else
188188
ch.addItem(configScope(name, child.description()));
189189
});
@@ -193,7 +193,7 @@ private static List<CompletionItem> topLevelItems(SpecNode.Scope spec) {
193193
var result = new ArrayList<CompletionItem>();
194194
spec.children().forEach((name, child) -> {
195195
if( child instanceof SpecNode.Option option ) {
196-
result.add(configOption(name, option.description(), option.type()));
196+
result.add(configOption(name, option.description(), option.types().get(0)));
197197
}
198198
else {
199199
result.add(configScope(name, child.description()));

src/main/java/nextflow/lsp/services/config/ConfigHoverProvider.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ protected String getHoverContent(List<ASTNode> nodeStack, SpecNode.Scope spec) {
104104
if( option != null ) {
105105
var description = StringGroovyMethods.stripIndent(option.description(), true).trim();
106106
var builder = new StringBuilder();
107-
builder.append(String.format("`%s (%s)`", fqName, Types.getName(option.type())));
107+
builder.append(String.format("`%s (%s)`", fqName, Types.getName(option.types().get(0))));
108108
builder.append("\n\n");
109109
builder.append(description);
110110
return builder.toString();

src/main/java/nextflow/lsp/services/config/ConfigSpecVisitor.java

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@
1717

1818
import java.util.ArrayList;
1919
import java.util.HashMap;
20+
import java.util.List;
2021
import java.util.Map;
2122
import java.util.Stack;
23+
import java.util.stream.Collectors;
2224

2325
import nextflow.config.ast.ConfigApplyBlockNode;
2426
import nextflow.config.ast.ConfigAssignNode;
@@ -34,6 +36,7 @@
3436
import nextflow.script.types.TypesEx;
3537
import org.codehaus.groovy.ast.ASTNode;
3638
import org.codehaus.groovy.ast.ClassHelper;
39+
import org.codehaus.groovy.ast.ClassNode;
3740
import org.codehaus.groovy.ast.expr.ConstantExpression;
3841
import org.codehaus.groovy.control.SourceUnit;
3942
import org.codehaus.groovy.control.messages.SyntaxErrorMessage;
@@ -163,14 +166,29 @@ public void visitConfigAssign(ConfigAssignNode node) {
163166
// validate type
164167
if( !typeChecking )
165168
return;
166-
var expectedType = option.type() != null ? ClassHelper.makeCached(option.type()) : ClassHelper.dynamicType();
169+
var expectedTypes = option.types().stream()
170+
.map(t -> ClassHelper.makeCached(t).getPlainNodeReference())
171+
.toList();
167172
var actualType = node.value.getType();
168-
if( !TypesEx.isAssignableFrom(expectedType, actualType) ) {
169-
var message = "Config option '" + fqName + "' with type " + TypesEx.getName(expectedType) + " cannot be assigned to value with type " + TypesEx.getName(actualType);
173+
if( !isAssignableFromAny(expectedTypes, actualType) ) {
174+
var validTypes = expectedTypes.stream()
175+
.map(cn -> TypesEx.getName(cn))
176+
.collect(Collectors.joining(", "));
177+
var message = "Config option '" + fqName + "' with cannot be assigned to value with type " + TypesEx.getName(actualType) + " -- valid types are: " + validTypes;
170178
addWarning(message, String.join(".", node.names), node.getLineNumber(), node.getColumnNumber());
171179
}
172180
}
173181

182+
private boolean isAssignableFromAny(List<ClassNode> targetTypes, ClassNode sourceType) {
183+
if( targetTypes.isEmpty() )
184+
return true;
185+
for( var targetType : targetTypes ) {
186+
if( TypesEx.isAssignableFrom(targetType, sourceType) )
187+
return true;
188+
}
189+
return false;
190+
}
191+
174192
@Override
175193
public void visitConfigBlock(ConfigBlockNode node) {
176194
// exclude selector name from scope stack

src/main/java/nextflow/lsp/spec/ConfigSpecFactory.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package nextflow.lsp.spec;
1717

1818
import java.io.IOException;
19+
import java.util.ArrayList;
1920
import java.util.Collections;
2021
import java.util.List;
2122
import java.util.Map;
@@ -110,8 +111,14 @@ private static SpecNode fromNode(Map<String,?> node) {
110111

111112
private static SpecNode.Option fromOption(Map<String,?> spec) {
112113
var description = (String) spec.get("description");
113-
var type = fromType(spec.get("type"));
114-
return new SpecNode.Option(description, type);
114+
var types = new ArrayList<Class>();
115+
types.add(fromType(spec.get("type")));
116+
if( spec.containsKey("additionalTypes") ) {
117+
var additionalTypes = (List) spec.get("additionalTypes");
118+
for( var additionalType : additionalTypes )
119+
types.add(fromType(additionalType));
120+
}
121+
return new SpecNode.Option(description, types);
115122
}
116123

117124
private static SpecNode.Placeholder fromPlaceholder(Map<String,?> spec) {
@@ -137,6 +144,7 @@ private static SpecNode.Scope fromScope(Map<String,?> spec) {
137144
Map.entry("Integer", Integer.class),
138145
Map.entry("int", Integer.class),
139146
Map.entry("List", List.class),
147+
Map.entry("Map", Map.class),
140148
Map.entry("MemoryUnit", MemoryUnit.class),
141149
Map.entry("Set", Set.class),
142150
Map.entry("String", String.class)

0 commit comments

Comments
 (0)