From e6a0fd62a94851e2e2d7560065e2c588c5daeeaf Mon Sep 17 00:00:00 2001 From: Noah Treuhaft Date: Tue, 24 Mar 2026 10:37:48 -0400 Subject: [PATCH 1/2] preserve top-level named type during upcast to fusion type Casting a value with a named type to a fusion type discards the name. Preseve it in the resulting fusion value instead. This change affects both upcast() and the fuse operator since it upcasts every value. This change does not affect the blend operator but I've added the same test cases to both runtime/ztests/op/blend.yaml and runtime/ztests/op/fuse.yaml to keep them in sync. --- runtime/sam/expr/function/upcast.go | 13 +++--------- runtime/ztests/expr/function/upcast.yaml | 20 ++++++++++-------- runtime/ztests/op/blend.yaml | 26 ++++++++++++++++++++++++ runtime/ztests/op/fuse.yaml | 26 ++++++++++++++++++++++++ 4 files changed, 66 insertions(+), 19 deletions(-) diff --git a/runtime/sam/expr/function/upcast.go b/runtime/sam/expr/function/upcast.go index 0dac4da5c..890c1b15d 100644 --- a/runtime/sam/expr/function/upcast.go +++ b/runtime/sam/expr/function/upcast.go @@ -41,8 +41,8 @@ func (u *Upcast) Cast(from super.Value, to super.Type) (super.Value, bool) { } func (u *Upcast) build(b *scode.Builder, typ super.Type, bytes scode.Bytes, to super.Type) bool { + typOrig := typ typ = super.TypeUnder(typ) - to = super.TypeUnder(to) switch to := to.(type) { case *super.TypeRecord: return u.toRecord(b, typ, bytes, to) @@ -57,9 +57,9 @@ func (u *Upcast) build(b *scode.Builder, typ super.Type, bytes scode.Bytes, to s case *super.TypeError: return u.toError(b, typ, bytes, to) case *super.TypeNamed: - return u.toNamed(b, typ, bytes, to) + return u.build(b, typ, bytes, to.Type) case *super.TypeFusion: - return u.toFusion(b, typ, bytes, to) + return u.toFusion(b, typOrig, bytes, to) default: if typ == to { b.Append(bytes) @@ -219,10 +219,3 @@ func (u *Upcast) toError(b *scode.Builder, typ super.Type, bytes scode.Bytes, to } return false } - -func (u *Upcast) toNamed(b *scode.Builder, typ super.Type, bytes scode.Bytes, to *super.TypeNamed) bool { - if namedType, ok := typ.(*super.TypeNamed); ok { - return u.build(b, namedType.Type, bytes, to.Type) - } - return false -} diff --git a/runtime/ztests/expr/function/upcast.yaml b/runtime/ztests/expr/function/upcast.yaml index 2c2a2c5e4..2cafc3351 100644 --- a/runtime/ztests/expr/function/upcast.yaml +++ b/runtime/ztests/expr/function/upcast.yaml @@ -6,24 +6,26 @@ vector: true input: | [[1,"a"],<[int8|string]>] [[1::int8,"a"],<[int8|string]>] - [1::=n,] - [{a:1::=n1}::=n2,] - [[1::=n1]::=n2,] - [|[1::=n1]|::=n2,] + [1::=n1,] + [{a:{b:1::=n1}::=n2}::=n3,] + [[[1::=n1]::=n2]::=n3,] + [|[|[1::=n1]|::=n2]|::=n3,] [|{1::=n1:2::=n2}|::=n3,] - [1::(n1=(n2=int64|n3=string)),] + [1::=n1::(n2=n1|(n3=string)),] ["a"::n1=enum(a,b),] + [1::=n1,] output: | error({message:"upcast: value not a subtype of [int8|string]",on:[1,"a"]}) [1::int8,"a"] 1::=n2 - {a:1::=n4}::=n3 - [1::=n4]::=n3 - |[1::=n4]|::=n3 + {a:{b:1::=n6}::=n5}::=n4 + [[1::=n6]::=n5]::=n4 + |[|[1::=n6]|::=n5]|::=n4 |{1::=n5:2::=n6}|::=n4 - 1::(n4=(n5=int64|(n6=string))) + 1::=n5::(n4=n5|(n6=string)) "a"::(n2=enum(a,b)) + fusion(1::(int64|string),) --- diff --git a/runtime/ztests/op/blend.yaml b/runtime/ztests/op/blend.yaml index b5e765472..b83200521 100644 --- a/runtime/ztests/op/blend.yaml +++ b/runtime/ztests/op/blend.yaml @@ -102,3 +102,29 @@ output: | [1,2]::[int64|string|null] ["foo"]::[int64|string|null] [null]::[int64|string|null] + +--- + +spq: blend + +vector: true + +input: | + 1::=p1 + {a:1::=r1}::=r2 + [1::=a1]::=a2 + |[1::=s1]|::=s2 + |{1::=m1:2::=m2}|::=m3 + "a"::en1=enum(a,b) + 1::u1=(u2=int64|u3=string) + error(1::=er1)::=er2 + +output: | + 1::(int64|(u3=string)|{a:r1=int64}|[a1=int64]||[s1=int64]|||{m1=int64:m2=int64}||enum(a,b)|error(er1=int64)) + {a:1::=r1}::(int64|(u3=string)|{a:r1}|[a1=int64]||[s1=int64]|||{m1=int64:m2=int64}||enum(a,b)|error(er1=int64)) + [1::=a1]::(int64|(u3=string)|{a:r1=int64}|[a1]||[s1=int64]|||{m1=int64:m2=int64}||enum(a,b)|error(er1=int64)) + |[1::=s1]|::(int64|(u3=string)|{a:r1=int64}|[a1=int64]||[s1]|||{m1=int64:m2=int64}||enum(a,b)|error(er1=int64)) + |{1::=m1:2::=m2}|::(int64|(u3=string)|{a:r1=int64}|[a1=int64]||[s1=int64]|||{m1:m2}||enum(a,b)|error(er1=int64)) + "a"::enum(a,b)::(int64|(u3=string)|{a:r1=int64}|[a1=int64]||[s1=int64]|||{m1=int64:m2=int64}||enum(a,b)|error(er1=int64)) + 1::(int64|(u3=string)|{a:r1=int64}|[a1=int64]||[s1=int64]|||{m1=int64:m2=int64}||enum(a,b)|error(er1=int64)) + error(1::=er1)::(int64|(u3=string)|{a:r1=int64}|[a1=int64]||[s1=int64]|||{m1=int64:m2=int64}||enum(a,b)|error(er1=int64)) diff --git a/runtime/ztests/op/fuse.yaml b/runtime/ztests/op/fuse.yaml index b0382c9a3..573dcc8d4 100644 --- a/runtime/ztests/op/fuse.yaml +++ b/runtime/ztests/op/fuse.yaml @@ -102,3 +102,29 @@ output: | fusion([fusion(1::(int64|string|null),),fusion(2::(int64|string|null),)],<[int64]>) fusion([fusion("foo"::(int64|string|null),)],<[string]>) fusion([fusion(null::(int64|string|null),)],<[null]>) + +--- + +spq: fuse + +vector: true + +input: | + 1::=p1 + {a:1::=r1}::=r2 + [1::=a1]::=a2 + |[1::=s1]|::=s2 + |{1::=m1:2::=m2}|::=m3 + "a"::en1=enum(a,b) + 1::u1=(u2=int64|u3=string) + error(1::=er1)::=er2 + +output: | + fusion(1::(int64|(u3=string)|{a:r1=int64}|[a1=int64]||[s1=int64]|||{m1=int64:m2=int64}||enum(a,b)|error(er1=int64)),) + fusion({a:1::=r1}::(int64|(u3=string)|{a:r1}|[a1=int64]||[s1=int64]|||{m1=int64:m2=int64}||enum(a,b)|error(er1=int64)),) + fusion([1::=a1]::(int64|(u3=string)|{a:r1=int64}|[a1]||[s1=int64]|||{m1=int64:m2=int64}||enum(a,b)|error(er1=int64)),) + fusion(|[1::=s1]|::(int64|(u3=string)|{a:r1=int64}|[a1=int64]||[s1]|||{m1=int64:m2=int64}||enum(a,b)|error(er1=int64)),) + fusion(|{1::=m1:2::=m2}|::(int64|(u3=string)|{a:r1=int64}|[a1=int64]||[s1=int64]|||{m1:m2}||enum(a,b)|error(er1=int64)),) + fusion("a"::enum(a,b)::(int64|(u3=string)|{a:r1=int64}|[a1=int64]||[s1=int64]|||{m1=int64:m2=int64}||enum(a,b)|error(er1=int64)),) + fusion(1::(int64|(u3=string)|{a:r1=int64}|[a1=int64]||[s1=int64]|||{m1=int64:m2=int64}||enum(a,b)|error(er1=int64)),) + fusion(error(1::=er1)::(int64|(u3=string)|{a:r1=int64}|[a1=int64]||[s1=int64]|||{m1=int64:m2=int64}||enum(a,b)|error(er1=int64)),) From baca70300c41a2e2fe25754b0ac8b8c7fb5296f5 Mon Sep 17 00:00:00 2001 From: Noah Treuhaft Date: Tue, 24 Mar 2026 10:37:48 -0400 Subject: [PATCH 2/2] handle named types in defuse() --- runtime/sam/expr/function/downcast.go | 10 ++-------- runtime/ztests/expr/function/defuse.yaml | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/runtime/sam/expr/function/downcast.go b/runtime/sam/expr/function/downcast.go index da9a99f9f..04697cd6c 100644 --- a/runtime/sam/expr/function/downcast.go +++ b/runtime/sam/expr/function/downcast.go @@ -44,6 +44,7 @@ func (d *downcast) downcast(b *scode.Builder, typ super.Type, bytes scode.Bytes, superBytes, _ := superType.Deref(d.sctx, bytes) return d.downcast(b, superType.Type, superBytes, to) } + typ = super.TypeUnder(typ) switch to := to.(type) { case *super.TypeRecord: return d.toRecord(b, typ, bytes, to) @@ -58,7 +59,7 @@ func (d *downcast) downcast(b *scode.Builder, typ super.Type, bytes scode.Bytes, case *super.TypeError: return d.toError(b, typ, bytes, to) case *super.TypeNamed: - return d.toNamed(b, typ, bytes, to) + return d.downcast(b, typ, bytes, to.Type) case *super.TypeFusion: // Can't downcast to a super type return false @@ -171,13 +172,6 @@ func (d *downcast) toError(b *scode.Builder, typ super.Type, bytes scode.Bytes, return false } -func (d *downcast) toNamed(b *scode.Builder, typ super.Type, bytes scode.Bytes, to *super.TypeNamed) bool { - if namedType, ok := typ.(*super.TypeNamed); ok { - return d.downcast(b, namedType.Type, bytes, to.Type) - } - return false -} - func (d *downcast) subTypeOf(typ super.Type, bytes scode.Bytes, types []super.Type) int { // XXX TBD we should make a subtype() function that returns true if a type is // a subtype of another and use that here and expose it to the language. diff --git a/runtime/ztests/expr/function/defuse.yaml b/runtime/ztests/expr/function/defuse.yaml index 286b59189..2822eee1e 100644 --- a/runtime/ztests/expr/function/defuse.yaml +++ b/runtime/ztests/expr/function/defuse.yaml @@ -19,3 +19,18 @@ input: &input | output: *input +--- + +spq: fuse | defuse(this) + +input: &input | + 1::=p1 + {a:{b:1::=r1}::=r2}::=r3 + [[1::=a1]::=a2]::=a3 + |[|[1::=s1]|::=s2]|::=s3 + |{1::=m1:2::=m2}|::=m3 + "a"::(en1=enum(a,b)) + 1::=n1::(n2=n1|(n3=string)) + error(1::=er1)::=er2 + +output: *input