an unconstrained named template parameter is not inferred through a cast and a nested generic call
Status
Minimal scenario
Two independent named template parameters, Stored and Lookup. Lookup is
the type of a value parameter, so it is always resolved from the call site.
Stored is used only as the target type of a cast from <ptr u8> and as a
parameter type of a nested generic call — never as the type of a value
parameter of the outer function itself.
(block
(pragma language overloads)
(fun eq_i64 ((var a i64) (var b i64)) -> bool
(block (return (== a b))))
(fun inner
((var data <ptr <named Stored (template readable mutable)>>)
(var key <named Lookup (template readable mutable)>)) -> bool
(block
(return (call eq_i64 (index data (: 0 i64)) key))))
(fun outer
((var raw <ptr u8>)
(var key <named Lookup (template readable mutable)>)) -> bool
(block
(var typed = (cast raw <ptr <named Stored (template readable mutable)>>))
(return (call inner typed key))))
(fun entry ((var raw <ptr u8>) (var key i64)) -> bool
(block (return (call outer raw key)))))
entry calls outer with key: i64, so Lookup is unambiguously i64.
Nothing in outer's own parameter list mentions Stored.
Actual behavior
Type annotation leaves Stored as the unresolved placeholder type Named
instead of unifying it with Lookup's concrete type (i64) through the
cast and the nested call. The cast to <ptr <named Stored ...>> produces
a value of type Named, (index data (: 0 i64)) likewise has type Named,
and passing it to eq_i64(i64, i64) fails:
Error: Аргумент #1: нельзя неявно преобразовать тип 'Named' к типу параметра 'Int'.
("Argument #1: cannot implicitly convert type 'Named' to parameter type
'Int'.")
If Stored and Lookup are expected to resolve to two different concrete
types, the failure mode changes but the root cause is the same. Replace
eq_i64/i64 above with two overloads of key_equal — (i64, i64) and
(string, string) — and call outer with key: string. Stored is still
left as Named, so resolving key_equal(Stored, Lookup) =
key_equal(Named, string) finds no matching overload:
Error: no matching overload for 'key_equal'
Expected behavior
outer's only generic input is key: Lookup. inner is called with
typed: <ptr Stored> and key: Lookup, and inner's body requires Stored
and Lookup to be compatible with eq_i64/key_equal. The resolver should
propagate Lookup's concrete type through the cast target type and the
nested call to determine Stored, the same way it unifies named template
parameters that appear directly as value-parameter types.
Workaround
Add an extra, otherwise-unused parameter to outer whose declared type is
<named Stored (template readable mutable)>, and pass a value of the
intended concrete type at the call site:
(fun outer
((var raw <ptr u8>)
(var key <named Lookup (template readable mutable)>)
(var witness <named Stored (template readable mutable)>)) -> bool
(block
(var typed = (cast raw <ptr <named Stored (template readable mutable)>>))
(return (call inner typed key))))
Calling (call outer raw key key) (passing key again as witness) lets
the resolver bind Stored from an ordinary value-parameter type, and both
__generic_outer$Int$Int and __generic_inner$Int$Int (or the $String$String
specializations in the overloaded variant) are produced correctly.
an unconstrained named template parameter is not inferred through a
castand a nested generic callStatus
externals/qumir/test/regtest/cases/corelang.<named T (template ...)>parameter thatappears only as a
casttarget type and as a parameter type of a nestedgeneric call, from the concrete type of a sibling template parameter.
Minimal scenario
Two independent named template parameters,
StoredandLookup.Lookupisthe type of a value parameter, so it is always resolved from the call site.
Storedis used only as the target type of acastfrom<ptr u8>and as aparameter type of a nested generic call — never as the type of a value
parameter of the outer function itself.
entrycallsouterwithkey: i64, soLookupis unambiguouslyi64.Nothing in
outer's own parameter list mentionsStored.Actual behavior
Type annotation leaves
Storedas the unresolved placeholder typeNamedinstead of unifying it with
Lookup's concrete type (i64) through thecastand the nested call. Thecastto<ptr <named Stored ...>>producesa value of type
Named,(index data (: 0 i64))likewise has typeNamed,and passing it to
eq_i64(i64, i64)fails:("Argument #1: cannot implicitly convert type 'Named' to parameter type
'Int'.")
If
StoredandLookupare expected to resolve to two different concretetypes, the failure mode changes but the root cause is the same. Replace
eq_i64/i64above with two overloads ofkey_equal—(i64, i64)and(string, string)— and callouterwithkey: string.Storedis stillleft as
Named, so resolvingkey_equal(Stored, Lookup)=key_equal(Named, string)finds no matching overload:Expected behavior
outer's only generic input iskey: Lookup.inneris called withtyped: <ptr Stored>andkey: Lookup, andinner's body requiresStoredand
Lookupto be compatible witheq_i64/key_equal. The resolver shouldpropagate
Lookup's concrete type through thecasttarget type and thenested call to determine
Stored, the same way it unifies named templateparameters that appear directly as value-parameter types.
Workaround
Add an extra, otherwise-unused parameter to
outerwhose declared type is<named Stored (template readable mutable)>, and pass a value of theintended concrete type at the call site:
(fun outer ((var raw <ptr u8>) (var key <named Lookup (template readable mutable)>) (var witness <named Stored (template readable mutable)>)) -> bool (block (var typed = (cast raw <ptr <named Stored (template readable mutable)>>)) (return (call inner typed key))))Calling
(call outer raw key key)(passingkeyagain aswitness) letsthe resolver bind
Storedfrom an ordinary value-parameter type, and both__generic_outer$Int$Intand__generic_inner$Int$Int(or the$String$Stringspecializations in the overloaded variant) are produced correctly.