From 64dbb5353219b04941f8aa27b2fde7bd1e7ebbf2 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Tue, 15 Sep 2026 10:52:45 +0200 Subject: [PATCH] Do not type callable parameters from closure arguments A closure argument used to add a \Closure parameter type, but a parameter that accepts a callable can take many shapes - a closure, a function-name string, a [$object, method] array, a first-class callable - so \Closure is too narrow. Treat a closure or arrow function argument as a poison type that leaves the parameter untyped, even when another type is also observed for it. --- internal/aggregate/aggregate.go | 6 ++++++ internal/apply/apply_test.go | 16 ++++++++++++---- internal/valuetype/valuetype.go | 6 +++++- internal/valuetype/valuetype_test.go | 4 ++-- 4 files changed, 25 insertions(+), 7 deletions(-) diff --git a/internal/aggregate/aggregate.go b/internal/aggregate/aggregate.go index d50367c4..f74a0624 100644 --- a/internal/aggregate/aggregate.go +++ b/internal/aggregate/aggregate.go @@ -66,6 +66,12 @@ func resolveGroups(groups map[string]map[string]struct{}) map[string]Resolved { } sort.Strings(types) + // a callable argument (a closure) leaves the parameter untyped, since a + // callable can take many shapes that must not be narrowed + if contains(types, "callable") { + continue + } + nullable := contains(types, "null") members := without(types, "null") if len(members) == 0 { diff --git a/internal/apply/apply_test.go b/internal/apply/apply_test.go index f4436dfd..0283c085 100644 --- a/internal/apply/apply_test.go +++ b/internal/apply/apply_test.go @@ -163,20 +163,28 @@ func TestApply(t *testing.T) { want: " 1);", }, - want: " 1", "object:Closure"}, + {"closure", "function () {}", "callable"}, + {"arrow function", "fn () => 1", "callable"}, {"class name fetch", "Foo::class", "string"}, {"new object", "new Money()", "object:Money"}, {"builtin int return", "strlen($x)", "int"},