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
'a accepts anything, so fnConstrained(42) compiles while TS rejects it. That is the same defect class as #177, pointed the other way: accepting code the library rejects.
The comment at the mapping site acknowledges the constraint is dropped, so this is a known simplification rather than an oversight — but it is unsound, and #189 declined to do the same thing for methods precisely on that ground.
The resulting asymmetry
After #189, the same TS shape gets opposite answers depending on whether it is a function or a method:
declaration
binding
function text<T extends string>(t: T)
('a) => 'a — unsound, accepts 42
class C { text<T extends string>(t: T) }
flagged string — honest
docs/TYPE_MAPPING.md's claim that method generics are mapped "like a standalone generic function's" therefore holds only for the unconstrained case.
Options
Match the method path — leave a constrained param unmapped so it stays flagged. Honest and consistent, but coarse: it discards information the bound does provide.
So the ordering is: fix #190's naming, then take option 2 in both paths and delete the asymmetry.
Also uncovered
Constructors and static methods still do not register signature type params at all, so static of<T>(x: T) stays a flagged string while the instance method gets 'a. Worth folding into whichever option is taken, so all four signature kinds agree.
Found during PR #189's review rounds. Pre-existing on
main; not introduced by that PR, but it is now inconsistent with the method path that PR added.The defect
buildFunctionIR(src/extract.mjs:~1443) maps every signature type parameter to a ReScript type variable, discarding theextendsconstraint:'aaccepts anything, sofnConstrained(42)compiles while TS rejects it. That is the same defect class as #177, pointed the other way: accepting code the library rejects.The comment at the mapping site acknowledges the constraint is dropped, so this is a known simplification rather than an oversight — but it is unsound, and #189 declined to do the same thing for methods precisely on that ground.
The resulting asymmetry
After #189, the same TS shape gets opposite answers depending on whether it is a function or a method:
function text<T extends string>(t: T)('a) => 'a— unsound, accepts42class C { text<T extends string>(t: T) }string— honestdocs/TYPE_MAPPING.md's claim that method generics are mapped "like a standalone generic function's" therefore holds only for the unconstrained case.Options
T extends string→string. Sound in the parameter position (at a call siteTis inferred from the argument, so anystringis admissible) and an honest ⚪ widening in a return position. This is the better answer, but it is currently blocked by Large numeric-literal unions generate unusable type names (v100OrV102Or…OrV511OrV1), blocking constrained-generic resolution #190: hono'sU extends ContentfulStatusCoderesolves to a ~60-member numeric union whose generated name isv100OrV102Or…OrV511OrV1, which then lands in every signature mentioning a status. Fix #177: two instantiations of one generic are two types (+ method generics become real type variables) #189 implemented and reverted exactly this for the same reason.So the ordering is: fix #190's naming, then take option 2 in both paths and delete the asymmetry.
Also uncovered
Constructors and static methods still do not register signature type params at all, so
static of<T>(x: T)stays a flaggedstringwhile the instance method gets'a. Worth folding into whichever option is taken, so all four signature kinds agree.