Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion workflow/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,12 @@ func sendTypeCompatibleWithInput(outType, inType reflect.Type) bool {
if outType == reflect.TypeFor[any]() {
return true
}
return outType == inType || outType.AssignableTo(inType) || (inType.Kind() == reflect.Interface && outType.Implements(inType))
// Compatibility (type-set overlap) is symmetric: the edge is valid when a
// concrete value can satisfy both sides. Also accept the case where the
// target input type is assignable to the sent type - e.g. an interface send
// type with a concrete target that implements it (source may emit that
// concrete value).
return outType == inType || outType.AssignableTo(inType) || inType.AssignableTo(outType)
}

func (wb *Builder) trackInputPort(port RequestPort) bool {
Expand Down
21 changes: 21 additions & 0 deletions workflow/builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1027,3 +1027,24 @@ func TestBuilder_ConditionalEdgeDoesNotDropIdempotentConditionlessEdge(t *testin
t.Fatalf("edges from start: conditional=%d conditionless=%d, want 1 and 1", conditional, conditionless)
}
}

type shapeForTypeCompat interface{ area() float64 }

type circleForTypeCompat struct{}

func (circleForTypeCompat) area() float64 { return 1 }

func TestBuilder_Validation_TypeCompatibility_InterfaceSendConcreteTarget(t *testing.T) {
// Source declares it sends the interface type; target accepts a concrete
// type that implements it. The source may emit that concrete value, so the
// edge is valid and must build (type-set overlap is symmetric).
source := newDeclaredSendExecutor[string]("source", reflect.TypeFor[shapeForTypeCompat]())
target := newTypedExecutor[circleForTypeCompat, string]("target")

_, err := workflow.NewBuilder(source).
AddEdge(source, target).
Build()
if err != nil {
t.Fatalf("expected interface-send/concrete-target edge to build, got: %v", err)
}
}
Loading