Constraint check compares a type parameter's TypeVar, not its instantiation: nested generic calls warn on code tsc accepts
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 48/100
- Issue type
- Bug
- Clarity
- Mostly clear
- Activity status
- Active
- Tech stack
- rust, typescript
Research direction
Start by tracing generic call argument type checking in the Rust compiler, using the reported TypeVar("F") versus instantiated argument type as the clue. Compare the nested generic call with the direct inQueryConnection(calculate) case, then run or add a regression case based on the reproducer. Done means the nested call compiles without the constraint warning while preserving correct behavior.
Written by the indexing model from the issue text.
Description
perry compile warns on code tsc --strict accepts, whenever the argument to a generic function is itself the result of a generic call. The compiler appears to check the uninstantiated type variable against the parameter's constraint instead of the type it was instantiated to, and no type variable can satisfy a function-shape constraint.
Benign: it is a warning, codegen continues, and the binary runs correctly. This is a diagnostic-quality bug, not a blocker — but it fires on ordinary decorator-style composition, so it is noisy on real code.
Reproducer
interface Rel { n: number }
function inQueryConnection<F extends (this: Rel, ...args: never[]) => Promise<any>>(fn: F): F {
return function (this: Rel, ...args: unknown[]) {
return fn.apply(this, args as never[]);
} as unknown as F;
}
function withDeferred<F extends (this: Rel, ...args: never[]) => unknown>(fn: F): F {
return function (this: Rel, ...args: never[]) {
return fn.apply(this, args);
} as unknown as F;
}
async function calculate(this: Rel, op?: string): Promise<number> {
return op ? 1 : this.n;
}
export const Calculations = {
calculate: inQueryConnection(withDeferred(calculate)),
};
console.log(`[repro] ${await Calculations.calculate.call({ n: 7 })}`);
Observed
Warning: Constraint violation in function 'inQueryConnection': TypeMismatch {
type_param: "F",
expected: Function(FunctionType { params: [("this", Named("Rel"), false),
("_rest", Array(Never), false)], return_type: Promise(Any),
is_async: false, is_generator: false }),
actual: TypeVar("F") }
The binary then compiles, links and prints [repro] 7, which is correct.
Expected
No warning. withDeferred(calculate) has type typeof calculate, which satisfies inQueryConnection's constraint. tsc --ignoreConfig --strict --target es2022 --module esnext --moduleResolution bundler --noEmit exits 0 on the same file (TypeScript 7.1.0-dev.20260920.1).
What narrows it
- It is not a bound mismatch. Widening the outer bound from
=> Promise<any>to=> unknown, so both bounds are identical, still warns — the message only changes toreturn_type: Unknown. So Perry compares against the correct bound and still rejectsTypeVar("F"). actual: TypeVar("F")is the tell: the argument is reported as the type variable, not as the type it was instantiated to.- Calling
inQueryConnection(calculate)directly — a non-generic argument — does not warn. The nesting is required.
Environment
- Perry
main@20fde48d, built from source, Linux x86_64, LLVM 22.1.8, rustc 1.101.0-nightly - Also reproduces on npm
@perryts/perry0.5.1520 and on from-source 0.5.1662
Found while compiling a TypeScript web framework ahead-of-time; the original site is a query-method wrapper composed exactly this way.
- Dominant language
- Rust
- Stars
- 4.9k
- Forks
- 166
- Avg merge
- 1h 19m
- Merged PRs (30d)
- 913
Getting set up
- Ships a Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from PerryTS/perry
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
Maintainers usually reply within 1 day
-
Difficulty 3/5 1-2 days Newbie friendliness 70/100
Maintainers usually reply within 1 day
-
Difficulty 3/5 1-2 days Newbie friendliness 73/100
Maintainers usually reply within 1 day
-
Difficulty 4/5 3-5 days Newbie friendliness 52/100
Maintainers usually reply within 1 day
Similar issues
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
project-robius/robrix#1177 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
ClickHouse/clickhouse-rs#497 ·
Maintainers usually reply within 1 day
-
enhancement
Difficulty 2/5 1-3 hours Newbie friendliness 79/100
Maintainers usually reply within 1 day
-
area:breg bug
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
registrystack/registry-stack#2307 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
farion1231/cc-switch#8696 · 1 comment ·
Maintainers usually reply within 1 day