Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Fix cast and instanceof with record types#7185

Merged
bentsherman merged 7 commits into
masterfrom
fix-cast-as-record-type
Jun 3, 2026
Merged

Fix cast and instanceof with record types#7185
bentsherman merged 7 commits into
masterfrom
fix-cast-as-record-type

Conversation

@bentsherman

Copy link
Copy Markdown
Member

This PR fixes the use of casting records to a named record type, for example:

workflow {
    record(id: '1', fastq: file('1.fastq')) as Sample
}

record Sample {
    id: String
    fastq: Path
}

Casting a record to a named record type should be a no-op at runtime. It is only used by the type checker to validate that the record satisfies the requirements of the record type.

If the cast is is not removed at runtime, the record would be cast to an instance of Sample, since record types are compiled to actual classes at runtime with an implicit constructor. This would break the runtime's assumption that all records are RecordMaps.

@bentsherman bentsherman requested a review from jorgee May 28, 2026 16:04
@bentsherman bentsherman requested a review from a team as a code owner May 28, 2026 16:04
@netlify

netlify Bot commented May 28, 2026

Copy link
Copy Markdown

Deploy Preview for nextflow-docs-staging canceled.

Name Link
🔨 Latest commit bc6ad68
🔍 Latest deploy log https://app.netlify.com/projects/nextflow-docs-staging/deploys/6a1e64d640a4be0008b642bb

@jorgee

jorgee commented May 28, 2026

Copy link
Copy Markdown
Contributor

In my opinion, this behaviour is strange or at least incomplete; a user could do something like this:

record Sample{
	id:String
	num: Integer
}

workflow{
	def a = record(id: "hello", num: 1) as Sample
	if (a instanceof Sample){
		println("It is a sample")
	}else{
		println("not a sample")
	}
}

You create a record, cast it to Sample and then check the type with instanceof and see that it is not really a Sample. Not sure, if it is possible to replace the instanceof by a method that checks the defined fields instead of the class. At least we should add a note describing it in the docs. (I haven't found anything about it)

@bentsherman

Copy link
Copy Markdown
Member Author

Good catch. I actually meant to implement a compiler transform for that as well, so that sample instanceof Sample becomes something like _instanceof_record(sample, Sample) which performs a runtime check. I guess that fell through the cracks during development

I will try to implement that in this PR

@bentsherman

Copy link
Copy Markdown
Member Author

@jorgee added the runtime check for instanceof, ready for review

@bentsherman bentsherman force-pushed the fix-cast-as-record-type branch from 2467cd2 to 998d11e Compare May 30, 2026 20:39
@bentsherman bentsherman changed the title Fix record cast with named record type Fix cast and instanceof with record types Jun 1, 2026
Signed-off-by: Ben Sherman <[email protected]>
@jorgee

jorgee commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

The new cast that redirects to asType is breaking a test that is casting a list to a Tuple.

Expected no exception to be thrown, but got 'nextflow.exception.ScriptCompilationException'
Expected no exception to be thrown, but got 'nextflow.exception.ScriptCompilationException'
	at spock.lang.Specification.noExceptionThrown(Specification.java:136)
	at nextflow.script.parser.v2.ScriptLoaderV2Test.should strip unsupported type annotations(ScriptLoaderV2Test.groovy:302)
Caused by: nextflow.exception.ScriptCompilationException: Script compilation failed
	at nextflow.script.parser.v2.ScriptLoaderV2.parse0(ScriptLoaderV2.groovy:129)
	at nextflow.script.parser.v2.ScriptLoaderV2.parse(ScriptLoaderV2.groovy:90)
	at nextflow.script.parser.v2.ScriptLoaderV2Test.should strip unsupported type annotations(ScriptLoaderV2Test.groovy:298)
Caused by: org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
Main: 3: The class nextflow.script.types.Tuple<java.lang.String, java.lang.String, java.lang.String> (supplied with 3 type parameters) refers to the class nextflow.script.types.Tuple which takes no parameters
 @ line 3, column 44.
   '1', '1.fastq', '2.fastq'] as Tuple<Stri
                             ^

Signed-off-by: Ben Sherman <[email protected]>
@bentsherman

Copy link
Copy Markdown
Member Author

Good catches. I was wondering about this too, whether to override all cast expressions or not. I updated the cast visitor to preserve the existing behavior and only inject the runtime check for things like as Sample and as List<Sample>

@bentsherman bentsherman requested a review from jorgee June 1, 2026 09:18
@jorgee

jorgee commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

I think the check of the existing Path is still happening in the case of generics. ['no-existing-file.txt'] as List<Path> is throwing the exeception. Maybe is out of the scope of the PR, but I am wondering why the cast of TypeHelper must check the existence of the file.

@bentsherman

Copy link
Copy Markdown
Member Author

Some users have asked to make checkIfExists: true the default in the file() function, because this is almost always what you want (i.e. reading a file), whereas you would only want checkIfExists: false when writing a new file. But we can't realistically do that because file() is used everywhere. It would be a big breaking change

Instead, I introduced a new default with the params block, where Path params verify the file existence, and new files must be declared as String. So the purpose of checking file existence is to verify the existence of input files before they are read. An optional file should be null rather than a reference to a missing file.

The as operator should have the same behavior as the params block since it has the same purpose (validating/converting external data to Nextflow types). The only difference being that params can cast command-line params from string to number/boolean/path.

Since this PR is meant to be a bug fix for 26.04, I restricted the changes to only affect new use cases (parameterized types and record types). But we should probably also apply this behavior to as Path in the future. I would consider it an aspect of the strict parser since it is just a stricter validation of what users are already doing.

@bentsherman bentsherman merged commit 0834c83 into master Jun 3, 2026
25 checks passed
@bentsherman bentsherman deleted the fix-cast-as-record-type branch June 3, 2026 07:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants