You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Type checker: == rejected between compatible but unequal types (records, maps, tuples) #7841
The == operator is rejected between a record type and an anonymous record with the same fields, even though the anonymous record is assignable to the record type (records are structurally typed).
MRE
nextflow.enable.types =true
record Sample {
id: String
}
def f(s: Sample) ->Boolean {
return s == record(id: 'a')
}
Actual
$ NXF_SYNTAX_PARSER=v2 nextflow lint main.nfError main.nf:8:12: The `==` operator is not defined for operands with types Sample and Record { id: String}│ 8 | return s == record(id: 'a')╰ | ^^^^^^^^^^^^^^^^^^^^
s == t (both Sample) and record(id: 'a') == record(id: 'b') are accepted.
$ NXF_SYNTAX_PARSER=v2 nextflow lint main.nfError main.nf:4:13: The `==` operator is not defined for operands with types Map<String, Integer> and Map<String, Float>│ 4 | println([a: 1] == [a: 1.0])╰ | ^^^^^^^^^^^^^^^^^^Error main.nf:5:13: The `==` operator is not defined for operands with types Tuple<Integer, Integer> and Tuple<Float, Integer>│ 5 | println(tuple(1, 2) == tuple(1.0, 2))╰ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
nextflow run prints true for all four. 1 == 1.0 and [1] == [1.0] (List<Integer> vs List<Float>) are accepted, so the checker is inconsistent.
Expected
No error. == should be allowed when either operand type is assignable to the other, for records, maps and tuples alike.
Version
Nextflow 26.10.0 and 26.10.3 (also present on current master)
Root cause
TypeCheckingVisitor.visitBinaryExpression, case COMPARE_EQUAL / COMPARE_NOT_EQUAL: it accepts only Types.isEqual(lhsType, rhsType) or a compareTo op. Types.isEqual compares the classes nominally, so a named record and a structurally compatible anonymous record are not equal. Checking Types.isAssignableFrom in either direction (which handles records structurally via isAssignableFromRecord) would fix it.
[1] == [1.0] passes only because the List shim declares compareTo(List<E>, List<E>) in ListOps, so the compareTo fallback resolves. Map has no compareTo op and Tuple has no shim, so they hit the Types.isEqual check, which compares generic arguments exactly.
changed the title [-]Type checker: `==` rejected between a record type and a compatible anonymous record[/-][+]Type checker: `==` rejected between compatible but unequal types (records, maps, tuples)[/+]on Oct 22, 2026
Activity
[-]Type checker: `==` rejected between a record type and a compatible anonymous record[/-][+]Type checker: `==` rejected between compatible but unequal types (records, maps, tuples)[/+]