Skip to content

Commit 91d5453

Browse files
committed
Type annotations
Signed-off-by: Ben Sherman <[email protected]>
1 parent 99f9edf commit 91d5453

File tree

28 files changed

+588
-231
lines changed

28 files changed

+588
-231
lines changed

docs/migrations/25-10.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,55 @@ This page summarizes the upcoming changes in Nextflow 25.10, which will be relea
88
This page is a work in progress and will be updated as features are finalized. It should not be considered complete until the 25.10 release.
99
:::
1010

11+
## New features
12+
13+
<h3>Type annotations</h3>
14+
15+
Type annotations are a way to denote the *type* of a variable. They are useful both for documenting and validating your pipeline code.
16+
17+
```nextflow
18+
workflow RNASEQ {
19+
take:
20+
reads: Channel<Path>
21+
index: Channel<Path>
22+
23+
main:
24+
samples_ch = QUANT( reads.combine(index) )
25+
26+
emit:
27+
samples: Channel<Path> = samples_ch
28+
}
29+
30+
def isSraId(id: String) -> Boolean {
31+
return id.startsWith('SRA')
32+
}
33+
```
34+
35+
The following declarations can be annotated with types:
36+
37+
- Pipeline parameters (the `params` block)
38+
- Workflow takes and emits
39+
- Function parameters and returns
40+
- Local variables
41+
- Closure parameters
42+
- Workflow outputs (the `output` block)
43+
44+
Type annotations can refer to any of the {ref}`standard types <stdlib-types>` as well as the following primitive types:
45+
46+
- Boolean
47+
- Integer
48+
- Number
49+
50+
Type annotations can be appended with `?` to denote that the value can be `null`:
51+
52+
```nextflow
53+
def x_opt: String? = null
54+
```
55+
56+
:::{note}
57+
While Nextflow inherited type annotations of the form `<type> <name>` from Groovy, this syntax was deprecated in the {ref}`strict syntax <strict-syntax-page>`. Groovy-style type annotations are still allowed for functions and local variables, but will be automatically converted to Nextflow-stype type annotations when formatting code with the language server or `nextflow lint`.
58+
:::
59+
1160
## Breaking changes
1261

1362
- The AWS Java SDK used by Nextflow was upgraded from v1 to v2, which introduced some breaking changes to the `aws.client` config options. See {ref}`the guide <aws-java-sdk-v2-page>` for details.

docs/reference/stdlib-types.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,20 +36,20 @@ See {ref}`channel-page` for an overview of channels. See {ref}`channel-factory`
3636

3737
A Duration represents a duration of time with millisecond precision.
3838

39-
A Duration can be created by adding a unit suffix to an integer (e.g. `1.h`), or more explicitly with `Duration.of()`:
39+
A Duration can be created by adding a unit suffix to an integer (e.g. `1.h`), or by explicitly casting to `Duration`:
4040

4141
```nextflow
4242
// integer with suffix
4343
oneDay = 24.h
4444
4545
// integer value (milliseconds)
46-
oneSecond = Duration.of(1000)
46+
oneSecond = 1000 as Duration
4747
4848
// simple string value
49-
oneHour = Duration.of('1h')
49+
oneHour = '1h' as Duration
5050
5151
// complex string value
52-
complexDuration = Duration.of('1day 6hours 3minutes 30seconds')
52+
complexDuration = '1day 6hours 3minutes 30seconds' as Duration
5353
```
5454

5555
The following suffixes are available:
@@ -352,17 +352,17 @@ Maps in Nextflow are backed by the [Java](https://docs.oracle.com/en/java/javase
352352

353353
A MemoryUnit represents a quantity of bytes.
354354

355-
A MemoryUnit can be created by adding a unit suffix to an integer (e.g. `1.GB`), or more explicitly with `MemoryUnit.of()`:
355+
A MemoryUnit can be created by adding a unit suffix to an integer (e.g. `1.GB`), or by explicitly casting to `MemoryUnit`:
356356

357357
```nextflow
358358
// integer with suffix
359359
oneMegabyte = 1.MB
360360
361361
// integer value (bytes)
362-
oneKilobyte = MemoryUnit.of(1024)
362+
oneKilobyte = 1024 as MemoryUnit
363363
364364
// string value
365-
oneGigabyte = MemoryUnit.of('1 GB')
365+
oneGigabyte = '1 GB' as MemoryUnit
366366
```
367367

368368
The following suffixes are available:

docs/strict-syntax.md

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -299,10 +299,22 @@ def str = 'hello'
299299
def meta = [:]
300300
```
301301

302-
:::{note}
303-
Because type annotations are useful for providing type checking at runtime, the language server will not report errors for Groovy-style type annotations at this time. Type annotations will be addressed in a future version of the Nextflow language specification.
302+
:::{versionadded} 25.10.0
304303
:::
305304

305+
Local variables can be declared with a type annotation:
306+
307+
```nextflow
308+
def a: Integer = 1
309+
def b: Integer = 2
310+
def (c: Integer, d: Integer) = [3, 4]
311+
def (e: Integer, f: Integer) = [5, 6]
312+
def str: String = 'hello'
313+
def meta: Map = [:]
314+
```
315+
316+
While Groovy-style type annotations are still supported, the linter and language server will automatically convert them to Nextflow-style type annotations when formatting code. Groovy-style type annotations will not be supported in a future version.
317+
306318
### Strings
307319

308320
Groovy supports a wide variety of strings, including multi-line strings, dynamic strings, slashy strings, multi-line dynamic slashy strings, and more.
@@ -368,21 +380,26 @@ def map = (Map) readJson(json) // soft cast
368380
def map = readJson(json) as Map // hard cast
369381
```
370382

371-
In the strict syntax, only hard casts are supported. However, hard casts are discouraged because they can cause unexpected behavior if used improperly. Groovy-style type annotations should be used instead:
383+
In the strict syntax, only hard casts are supported.
384+
385+
When casting a value to a different type, it is always better to use an explicit method if one is available. For example, to parse a string as a number:
372386

373387
```groovy
374-
def Map map = readJson(json)
388+
def x = '42' as Integer
389+
def x = '42'.toInteger() // preferred
375390
```
376391

377-
Nextflow will raise an error at runtime if the `readJson()` function does not return a `Map`.
392+
:::{versionadded} 25.10.0
393+
:::
378394

379-
When converting a value to a different type, it is better to use an explicit method rather than a cast. For example, to parse a string as a number:
395+
In cases where a function returns an unknown type, use a type annotation:
380396

381397
```groovy
382-
def x = '42' as Integer
383-
def x = '42'.toInteger() // preferred
398+
def map: Map = readJson(json)
384399
```
385400

401+
Nextflow will raise an error at runtime if the `readJson()` function does not return a `Map`.
402+
386403
### Process env inputs and outputs
387404

388405
In Nextflow DSL2, the name of a process `env` input/output can be specified with or without quotes:

modules/nextflow/src/main/groovy/nextflow/script/parser/v2/ScriptCompiler.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@ public class ScriptCompiler {
6868
"java.nio.file.Path",
6969
"nextflow.Channel",
7070
"nextflow.util.Duration",
71-
"nextflow.util.MemoryUnit"
71+
"nextflow.util.MemoryUnit",
72+
"nextflow.util.VersionNumber"
7273
);
7374
private static final String MAIN_CLASS_NAME = "Main";
7475
private static final String BASE_CLASS_NAME = "nextflow.script.BaseScript";

modules/nextflow/src/main/groovy/nextflow/util/ArrayBag.groovy

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,13 @@
1515
*/
1616

1717
package nextflow.util
18+
1819
import com.esotericsoftware.kryo.Kryo
1920
import com.esotericsoftware.kryo.KryoSerializable
2021
import com.esotericsoftware.kryo.io.Input
2122
import com.esotericsoftware.kryo.io.Output
2223
import groovy.transform.CompileStatic
24+
import nextflow.script.types.Bag
2325
import org.codehaus.groovy.runtime.InvokerHelper
2426

2527
/**

modules/nf-commons/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ sourceSets {
2525
}
2626

2727
dependencies {
28+
api(project(':nf-lang'))
2829
api "ch.qos.logback:logback-classic:1.5.18"
2930
api "org.apache.groovy:groovy:4.0.27"
3031
api "org.apache.groovy:groovy-nio:4.0.27"

modules/nf-commons/src/main/nextflow/util/Duration.groovy

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import java.util.concurrent.TimeUnit
2222
import groovy.transform.CompileStatic
2323
import groovy.transform.EqualsAndHashCode
2424
import groovy.util.logging.Slf4j
25+
import nextflow.script.types.Duration as IDuration
2526
import org.apache.commons.lang.time.DurationFormatUtils
2627
/**
2728
* A simple time duration representation
@@ -31,7 +32,7 @@ import org.apache.commons.lang.time.DurationFormatUtils
3132
@Slf4j
3233
@CompileStatic
3334
@EqualsAndHashCode(includes = 'durationInMillis')
34-
class Duration implements Comparable<Duration>, Serializable, Cloneable {
35+
class Duration implements IDuration, Comparable<Duration>, Serializable, Cloneable {
3536

3637
static private final FORMAT = ~/^(\d+\.?\d*)\s*([a-zA-Z]+)/
3738

@@ -215,6 +216,7 @@ class Duration implements Comparable<Duration>, Serializable, Cloneable {
215216
new Duration(java.time.Duration.between(start, end).toMillis())
216217
}
217218

219+
@Override
218220
long toMillis() {
219221
durationInMillis
220222
}
@@ -223,6 +225,7 @@ class Duration implements Comparable<Duration>, Serializable, Cloneable {
223225
durationInMillis
224226
}
225227

228+
@Override
226229
long toSeconds() {
227230
TimeUnit.MILLISECONDS.toSeconds(durationInMillis)
228231
}
@@ -231,6 +234,7 @@ class Duration implements Comparable<Duration>, Serializable, Cloneable {
231234
toSeconds()
232235
}
233236

237+
@Override
234238
long toMinutes() {
235239
TimeUnit.MILLISECONDS.toMinutes(durationInMillis)
236240
}
@@ -239,6 +243,7 @@ class Duration implements Comparable<Duration>, Serializable, Cloneable {
239243
toMinutes()
240244
}
241245

246+
@Override
242247
long toHours() {
243248
TimeUnit.MILLISECONDS.toHours(durationInMillis)
244249
}
@@ -247,6 +252,7 @@ class Duration implements Comparable<Duration>, Serializable, Cloneable {
247252
toHours()
248253
}
249254

255+
@Override
250256
long toDays() {
251257
TimeUnit.MILLISECONDS.toDays(durationInMillis)
252258
}

modules/nf-commons/src/main/nextflow/util/HashBuilder.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import nextflow.extension.FilesEx;
4747
import nextflow.file.FileHolder;
4848
import nextflow.io.SerializableMarker;
49+
import nextflow.script.types.Bag;
4950
import org.slf4j.Logger;
5051
import org.slf4j.LoggerFactory;
5152

modules/nf-commons/src/main/nextflow/util/MemoryUnit.groovy

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,15 @@ import java.util.regex.Pattern
2222

2323
import groovy.transform.CompileStatic
2424
import groovy.transform.EqualsAndHashCode
25+
import nextflow.script.types.MemoryUnit as IMemoryUnit
2526
/**
2627
* Represent a memory unit
2728
*
2829
* @author Paolo Di Tommaso <[email protected]>
2930
*/
3031
@CompileStatic
3132
@EqualsAndHashCode(includes = 'size', includeFields = true)
32-
class MemoryUnit implements Comparable<MemoryUnit>, Serializable, Cloneable {
33+
class MemoryUnit implements IMemoryUnit, Comparable<MemoryUnit>, Serializable, Cloneable {
3334

3435
final static public MemoryUnit ZERO = new MemoryUnit(0)
3536

@@ -95,20 +96,24 @@ class MemoryUnit implements Comparable<MemoryUnit>, Serializable, Cloneable {
9596

9697
}
9798

99+
@Override
98100
long toBytes() {
99101
size
100102
}
101103

102104
long getBytes() { size }
103105

106+
@Override
104107
long toKilo() { size >> 10 }
105108

106109
long getKilo() { size >> 10 }
107110

111+
@Override
108112
long toMega() { size >> 20 }
109113

110114
long getMega() { size >> 20 }
111115

116+
@Override
112117
long toGiga() { size >> 30 }
113118

114119
long getGiga() { size >> 30 }
@@ -181,6 +186,7 @@ class MemoryUnit implements Comparable<MemoryUnit>, Serializable, Cloneable {
181186
*
182187
* @param unit String expressing memory unit in bytes, e.g. KB, MB, GB
183188
*/
189+
@Override
184190
long toUnit(String unit){
185191
int p = UNITS.indexOf(unit)
186192
if (p==-1)

modules/nf-commons/src/main/nextflow/util/VersionNumber.groovy

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,15 @@ import java.util.regex.Pattern
2020

2121
import groovy.transform.CompileStatic
2222
import nextflow.extension.Bolts
23+
import nextflow.script.types.VersionNumber as IVersionNumber
2324

2425
/**
2526
* Model a semantic version number
2627
*
2728
* @author Paolo Di Tommaso <[email protected]>
2829
*/
2930
@CompileStatic
30-
class VersionNumber implements Comparable {
31+
class VersionNumber implements IVersionNumber, Comparable {
3132

3233
static private Pattern CHECK = ~/\s*([!<=>]+)?\s*([0-9A-Za-z\-_\.]+)(\+)?/
3334

@@ -62,16 +63,19 @@ class VersionNumber implements Comparable {
6263
/**
6364
* @return The major version number ie. the first component
6465
*/
66+
@Override
6567
String getMajor() { version[0] }
6668

6769
/**
6870
* @return The minor version number ie. the second component
6971
*/
72+
@Override
7073
String getMinor() { version[1] }
7174

7275
/**
7376
* @return The minor version number ie. the third component
7477
*/
78+
@Override
7579
String getPatch() { version[2] }
7680

7781
/**
@@ -177,6 +181,7 @@ class VersionNumber implements Comparable {
177181
* @param condition
178182
* @return
179183
*/
184+
@Override
180185
boolean matches(String condition) {
181186
for( String token : condition.tokenize(',')) {
182187
if( !matches0(token) )

0 commit comments

Comments
 (0)