From 5e5c7f25cbf8768584e766cb6e1dacb76e6cbce2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 18 Nov 2025 22:32:17 +0000 Subject: [PATCH 1/4] Initial plan From 1d27d14b676b56fb38f04c81c897b6f591cb08b3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 18 Nov 2025 22:51:09 +0000 Subject: [PATCH 2/4] Fix panic on type predicate parameter mismatch with bounds checking Co-authored-by: RyanCavanaugh <6685088+RyanCavanaugh@users.noreply.github.com> --- internal/checker/checker_test.go | 65 ++++++++++++++++++++++++++++++++ internal/checker/flow.go | 4 +- 2 files changed, 67 insertions(+), 2 deletions(-) diff --git a/internal/checker/checker_test.go b/internal/checker/checker_test.go index 2dcda5d528..8beb0c1c44 100644 --- a/internal/checker/checker_test.go +++ b/internal/checker/checker_test.go @@ -80,6 +80,71 @@ func TestCheckSrcCompiler(t *testing.T) { p.CheckSourceFiles(t.Context(), nil) } +func TestTypePredicateParameterMismatch(t *testing.T) { + t.Parallel() + + // This test verifies that the checker doesn't panic when a type predicate + // references a parameter name that doesn't match any actual function parameter. + // The issue was that getTypePredicateArgument would try to access the arguments + // array with a negative index (-1) when the parameter name wasn't found. + content := `type TypeA = { kind: 'a' }; +type TypeB = { kind: 'b' }; +type UnionType = TypeA | TypeB; + +function isTypeA( + _value: UnionType +): value is TypeA { // "value" doesn't match parameter "_value" + return true; +} + +function test(input: UnionType): void { + if (isTypeA(input)) { + console.log(input.kind); + } +}` + fs := vfstest.FromMap(map[string]string{ + "/test.ts": content, + "/tsconfig.json": ` + { + "compilerOptions": { + "target": "es2015", + "module": "commonjs", + "strict": true + }, + "files": ["test.ts"] + } + `, + }, false /*useCaseSensitiveFileNames*/) + fs = bundled.WrapFS(fs) + + cd := "/" + host := compiler.NewCompilerHost(cd, fs, bundled.LibPath(), nil, nil) + + parsed, errors := tsoptions.GetParsedCommandLineOfConfigFile("/tsconfig.json", &core.CompilerOptions{}, host, nil) + assert.Equal(t, len(errors), 0, "Expected no errors in parsed command line") + + p := compiler.NewProgram(compiler.ProgramOptions{ + Config: parsed, + Host: host, + }) + + // This should not panic - it should report a diagnostic error instead + diags := p.GetSemanticDiagnostics(t.Context(), nil) + + // We expect at least one diagnostic error (TS1225: Cannot find parameter 'value') + assert.Assert(t, len(diags) > 0, "Expected at least one diagnostic error") + + // Verify the expected error code is present + foundExpectedError := false + for _, diag := range diags { + if diag.Code() == 1225 { // TS1225: Cannot find parameter + foundExpectedError = true + break + } + } + assert.Assert(t, foundExpectedError, "Expected to find error TS1225 (Cannot find parameter)") +} + func BenchmarkNewChecker(b *testing.B) { repo.SkipIfNoTypeScriptSubmodule(b) fs := osvfs.FS() diff --git a/internal/checker/flow.go b/internal/checker/flow.go index 1952dd2ba9..8df30cad40 100644 --- a/internal/checker/flow.go +++ b/internal/checker/flow.go @@ -2405,7 +2405,7 @@ func (c *Checker) typeMaybeAssignableTo(source *Type, target *Type) bool { func (c *Checker) getTypePredicateArgument(predicate *TypePredicate, callExpression *ast.Node) *ast.Node { if predicate.kind == TypePredicateKindIdentifier || predicate.kind == TypePredicateKindAssertsIdentifier { arguments := callExpression.Arguments() - if int(predicate.parameterIndex) < len(arguments) { + if predicate.parameterIndex >= 0 && int(predicate.parameterIndex) < len(arguments) { return arguments[predicate.parameterIndex] } } else { @@ -2496,7 +2496,7 @@ func (c *Checker) isReachableFlowNodeWorker(f *FlowState, flow *ast.FlowNode, no case flags&ast.FlowFlagsCall != 0: if signature := c.getEffectsSignature(flow.Node); signature != nil { if predicate := c.getTypePredicateOfSignature(signature); predicate != nil && predicate.kind == TypePredicateKindAssertsIdentifier && predicate.t == nil { - if arguments := flow.Node.Arguments(); int(predicate.parameterIndex) < len(arguments) && c.isFalseExpression(arguments[predicate.parameterIndex]) { + if arguments := flow.Node.Arguments(); predicate.parameterIndex >= 0 && int(predicate.parameterIndex) < len(arguments) && c.isFalseExpression(arguments[predicate.parameterIndex]) { return false } } From cc15e4aaa147d6c9c7fa685ecc7a76d1676445ee Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 18 Nov 2025 23:37:05 +0000 Subject: [PATCH 3/4] Add second test case for asserts predicate and convert to testdata files Co-authored-by: RyanCavanaugh <6685088+RyanCavanaugh@users.noreply.github.com> --- internal/checker/checker_test.go | 65 ------------------- .../assertsPredicateParameterMismatch.ts | 19 ++++++ .../typePredicateParameterMismatch.ts | 21 ++++++ 3 files changed, 40 insertions(+), 65 deletions(-) create mode 100644 testdata/tests/cases/compiler/assertsPredicateParameterMismatch.ts create mode 100644 testdata/tests/cases/compiler/typePredicateParameterMismatch.ts diff --git a/internal/checker/checker_test.go b/internal/checker/checker_test.go index 8beb0c1c44..2dcda5d528 100644 --- a/internal/checker/checker_test.go +++ b/internal/checker/checker_test.go @@ -80,71 +80,6 @@ func TestCheckSrcCompiler(t *testing.T) { p.CheckSourceFiles(t.Context(), nil) } -func TestTypePredicateParameterMismatch(t *testing.T) { - t.Parallel() - - // This test verifies that the checker doesn't panic when a type predicate - // references a parameter name that doesn't match any actual function parameter. - // The issue was that getTypePredicateArgument would try to access the arguments - // array with a negative index (-1) when the parameter name wasn't found. - content := `type TypeA = { kind: 'a' }; -type TypeB = { kind: 'b' }; -type UnionType = TypeA | TypeB; - -function isTypeA( - _value: UnionType -): value is TypeA { // "value" doesn't match parameter "_value" - return true; -} - -function test(input: UnionType): void { - if (isTypeA(input)) { - console.log(input.kind); - } -}` - fs := vfstest.FromMap(map[string]string{ - "/test.ts": content, - "/tsconfig.json": ` - { - "compilerOptions": { - "target": "es2015", - "module": "commonjs", - "strict": true - }, - "files": ["test.ts"] - } - `, - }, false /*useCaseSensitiveFileNames*/) - fs = bundled.WrapFS(fs) - - cd := "/" - host := compiler.NewCompilerHost(cd, fs, bundled.LibPath(), nil, nil) - - parsed, errors := tsoptions.GetParsedCommandLineOfConfigFile("/tsconfig.json", &core.CompilerOptions{}, host, nil) - assert.Equal(t, len(errors), 0, "Expected no errors in parsed command line") - - p := compiler.NewProgram(compiler.ProgramOptions{ - Config: parsed, - Host: host, - }) - - // This should not panic - it should report a diagnostic error instead - diags := p.GetSemanticDiagnostics(t.Context(), nil) - - // We expect at least one diagnostic error (TS1225: Cannot find parameter 'value') - assert.Assert(t, len(diags) > 0, "Expected at least one diagnostic error") - - // Verify the expected error code is present - foundExpectedError := false - for _, diag := range diags { - if diag.Code() == 1225 { // TS1225: Cannot find parameter - foundExpectedError = true - break - } - } - assert.Assert(t, foundExpectedError, "Expected to find error TS1225 (Cannot find parameter)") -} - func BenchmarkNewChecker(b *testing.B) { repo.SkipIfNoTypeScriptSubmodule(b) fs := osvfs.FS() diff --git a/testdata/tests/cases/compiler/assertsPredicateParameterMismatch.ts b/testdata/tests/cases/compiler/assertsPredicateParameterMismatch.ts new file mode 100644 index 0000000000..9491593b96 --- /dev/null +++ b/testdata/tests/cases/compiler/assertsPredicateParameterMismatch.ts @@ -0,0 +1,19 @@ +// @strict: true +// @noemit: true + +// This test verifies that the checker doesn't panic when an assertion predicate +// references a parameter name that doesn't match any actual function parameter. +// This specifically tests the code path in isReachableFlowNodeWorker. + +function assertCondition( + _condition: boolean +): asserts condition { // "condition" doesn't match parameter "_condition" + if (!_condition) { + throw new Error('Condition failed'); + } +} + +function test(): void { + assertCondition(false); + console.log("unreachable"); +} diff --git a/testdata/tests/cases/compiler/typePredicateParameterMismatch.ts b/testdata/tests/cases/compiler/typePredicateParameterMismatch.ts new file mode 100644 index 0000000000..c4729d1995 --- /dev/null +++ b/testdata/tests/cases/compiler/typePredicateParameterMismatch.ts @@ -0,0 +1,21 @@ +// @strict: true +// @noemit: true + +// This test verifies that the checker doesn't panic when a type predicate +// references a parameter name that doesn't match any actual function parameter. + +type TypeA = { kind: 'a' }; +type TypeB = { kind: 'b' }; +type UnionType = TypeA | TypeB; + +function isTypeA( + _value: UnionType +): value is TypeA { // "value" doesn't match parameter "_value" + return true; +} + +function test(input: UnionType): void { + if (isTypeA(input)) { + console.log(input.kind); + } +} From e8d97503d345864dc4518a0167ff069be3924110 Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Tue, 18 Nov 2025 15:53:42 -0800 Subject: [PATCH 4/4] Add baselines --- ...sertsPredicateParameterMismatch.errors.txt | 23 +++++++++ .../assertsPredicateParameterMismatch.symbols | 34 +++++++++++++ .../assertsPredicateParameterMismatch.types | 41 +++++++++++++++ .../typePredicateParameterMismatch.errors.txt | 25 +++++++++ .../typePredicateParameterMismatch.symbols | 51 +++++++++++++++++++ .../typePredicateParameterMismatch.types | 48 +++++++++++++++++ 6 files changed, 222 insertions(+) create mode 100644 testdata/baselines/reference/compiler/assertsPredicateParameterMismatch.errors.txt create mode 100644 testdata/baselines/reference/compiler/assertsPredicateParameterMismatch.symbols create mode 100644 testdata/baselines/reference/compiler/assertsPredicateParameterMismatch.types create mode 100644 testdata/baselines/reference/compiler/typePredicateParameterMismatch.errors.txt create mode 100644 testdata/baselines/reference/compiler/typePredicateParameterMismatch.symbols create mode 100644 testdata/baselines/reference/compiler/typePredicateParameterMismatch.types diff --git a/testdata/baselines/reference/compiler/assertsPredicateParameterMismatch.errors.txt b/testdata/baselines/reference/compiler/assertsPredicateParameterMismatch.errors.txt new file mode 100644 index 0000000000..39b13ba38a --- /dev/null +++ b/testdata/baselines/reference/compiler/assertsPredicateParameterMismatch.errors.txt @@ -0,0 +1,23 @@ +assertsPredicateParameterMismatch.ts(7,12): error TS1225: Cannot find parameter 'condition'. + + +==== assertsPredicateParameterMismatch.ts (1 errors) ==== + // This test verifies that the checker doesn't panic when an assertion predicate + // references a parameter name that doesn't match any actual function parameter. + // This specifically tests the code path in isReachableFlowNodeWorker. + + function assertCondition( + _condition: boolean + ): asserts condition { // "condition" doesn't match parameter "_condition" + ~~~~~~~~~ +!!! error TS1225: Cannot find parameter 'condition'. + if (!_condition) { + throw new Error('Condition failed'); + } + } + + function test(): void { + assertCondition(false); + console.log("unreachable"); + } + \ No newline at end of file diff --git a/testdata/baselines/reference/compiler/assertsPredicateParameterMismatch.symbols b/testdata/baselines/reference/compiler/assertsPredicateParameterMismatch.symbols new file mode 100644 index 0000000000..bbeb60ccf3 --- /dev/null +++ b/testdata/baselines/reference/compiler/assertsPredicateParameterMismatch.symbols @@ -0,0 +1,34 @@ +//// [tests/cases/compiler/assertsPredicateParameterMismatch.ts] //// + +=== assertsPredicateParameterMismatch.ts === +// This test verifies that the checker doesn't panic when an assertion predicate +// references a parameter name that doesn't match any actual function parameter. +// This specifically tests the code path in isReachableFlowNodeWorker. + +function assertCondition( +>assertCondition : Symbol(assertCondition, Decl(assertsPredicateParameterMismatch.ts, 0, 0)) + + _condition: boolean +>_condition : Symbol(_condition, Decl(assertsPredicateParameterMismatch.ts, 4, 25)) + +): asserts condition { // "condition" doesn't match parameter "_condition" + if (!_condition) { +>_condition : Symbol(_condition, Decl(assertsPredicateParameterMismatch.ts, 4, 25)) + + throw new Error('Condition failed'); +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + } +} + +function test(): void { +>test : Symbol(test, Decl(assertsPredicateParameterMismatch.ts, 10, 1)) + + assertCondition(false); +>assertCondition : Symbol(assertCondition, Decl(assertsPredicateParameterMismatch.ts, 0, 0)) + + console.log("unreachable"); +>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +} + diff --git a/testdata/baselines/reference/compiler/assertsPredicateParameterMismatch.types b/testdata/baselines/reference/compiler/assertsPredicateParameterMismatch.types new file mode 100644 index 0000000000..1f05e06e53 --- /dev/null +++ b/testdata/baselines/reference/compiler/assertsPredicateParameterMismatch.types @@ -0,0 +1,41 @@ +//// [tests/cases/compiler/assertsPredicateParameterMismatch.ts] //// + +=== assertsPredicateParameterMismatch.ts === +// This test verifies that the checker doesn't panic when an assertion predicate +// references a parameter name that doesn't match any actual function parameter. +// This specifically tests the code path in isReachableFlowNodeWorker. + +function assertCondition( +>assertCondition : (_condition: boolean) => asserts condition + + _condition: boolean +>_condition : boolean + +): asserts condition { // "condition" doesn't match parameter "_condition" + if (!_condition) { +>!_condition : boolean +>_condition : boolean + + throw new Error('Condition failed'); +>new Error('Condition failed') : Error +>Error : ErrorConstructor +>'Condition failed' : "Condition failed" + } +} + +function test(): void { +>test : () => void + + assertCondition(false); +>assertCondition(false) : void +>assertCondition : (_condition: boolean) => asserts condition +>false : false + + console.log("unreachable"); +>console.log("unreachable") : void +>console.log : (...data: any[]) => void +>console : Console +>log : (...data: any[]) => void +>"unreachable" : "unreachable" +} + diff --git a/testdata/baselines/reference/compiler/typePredicateParameterMismatch.errors.txt b/testdata/baselines/reference/compiler/typePredicateParameterMismatch.errors.txt new file mode 100644 index 0000000000..6c3037be78 --- /dev/null +++ b/testdata/baselines/reference/compiler/typePredicateParameterMismatch.errors.txt @@ -0,0 +1,25 @@ +typePredicateParameterMismatch.ts(10,4): error TS1225: Cannot find parameter 'value'. + + +==== typePredicateParameterMismatch.ts (1 errors) ==== + // This test verifies that the checker doesn't panic when a type predicate + // references a parameter name that doesn't match any actual function parameter. + + type TypeA = { kind: 'a' }; + type TypeB = { kind: 'b' }; + type UnionType = TypeA | TypeB; + + function isTypeA( + _value: UnionType + ): value is TypeA { // "value" doesn't match parameter "_value" + ~~~~~ +!!! error TS1225: Cannot find parameter 'value'. + return true; + } + + function test(input: UnionType): void { + if (isTypeA(input)) { + console.log(input.kind); + } + } + \ No newline at end of file diff --git a/testdata/baselines/reference/compiler/typePredicateParameterMismatch.symbols b/testdata/baselines/reference/compiler/typePredicateParameterMismatch.symbols new file mode 100644 index 0000000000..6efd825513 --- /dev/null +++ b/testdata/baselines/reference/compiler/typePredicateParameterMismatch.symbols @@ -0,0 +1,51 @@ +//// [tests/cases/compiler/typePredicateParameterMismatch.ts] //// + +=== typePredicateParameterMismatch.ts === +// This test verifies that the checker doesn't panic when a type predicate +// references a parameter name that doesn't match any actual function parameter. + +type TypeA = { kind: 'a' }; +>TypeA : Symbol(TypeA, Decl(typePredicateParameterMismatch.ts, 0, 0)) +>kind : Symbol(kind, Decl(typePredicateParameterMismatch.ts, 3, 14)) + +type TypeB = { kind: 'b' }; +>TypeB : Symbol(TypeB, Decl(typePredicateParameterMismatch.ts, 3, 27)) +>kind : Symbol(kind, Decl(typePredicateParameterMismatch.ts, 4, 14)) + +type UnionType = TypeA | TypeB; +>UnionType : Symbol(UnionType, Decl(typePredicateParameterMismatch.ts, 4, 27)) +>TypeA : Symbol(TypeA, Decl(typePredicateParameterMismatch.ts, 0, 0)) +>TypeB : Symbol(TypeB, Decl(typePredicateParameterMismatch.ts, 3, 27)) + +function isTypeA( +>isTypeA : Symbol(isTypeA, Decl(typePredicateParameterMismatch.ts, 5, 31)) + + _value: UnionType +>_value : Symbol(_value, Decl(typePredicateParameterMismatch.ts, 7, 17)) +>UnionType : Symbol(UnionType, Decl(typePredicateParameterMismatch.ts, 4, 27)) + +): value is TypeA { // "value" doesn't match parameter "_value" +>TypeA : Symbol(TypeA, Decl(typePredicateParameterMismatch.ts, 0, 0)) + + return true; +} + +function test(input: UnionType): void { +>test : Symbol(test, Decl(typePredicateParameterMismatch.ts, 11, 1)) +>input : Symbol(input, Decl(typePredicateParameterMismatch.ts, 13, 14)) +>UnionType : Symbol(UnionType, Decl(typePredicateParameterMismatch.ts, 4, 27)) + + if (isTypeA(input)) { +>isTypeA : Symbol(isTypeA, Decl(typePredicateParameterMismatch.ts, 5, 31)) +>input : Symbol(input, Decl(typePredicateParameterMismatch.ts, 13, 14)) + + console.log(input.kind); +>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>input.kind : Symbol(kind, Decl(typePredicateParameterMismatch.ts, 3, 14), Decl(typePredicateParameterMismatch.ts, 4, 14)) +>input : Symbol(input, Decl(typePredicateParameterMismatch.ts, 13, 14)) +>kind : Symbol(kind, Decl(typePredicateParameterMismatch.ts, 3, 14), Decl(typePredicateParameterMismatch.ts, 4, 14)) + } +} + diff --git a/testdata/baselines/reference/compiler/typePredicateParameterMismatch.types b/testdata/baselines/reference/compiler/typePredicateParameterMismatch.types new file mode 100644 index 0000000000..92c77ed7e9 --- /dev/null +++ b/testdata/baselines/reference/compiler/typePredicateParameterMismatch.types @@ -0,0 +1,48 @@ +//// [tests/cases/compiler/typePredicateParameterMismatch.ts] //// + +=== typePredicateParameterMismatch.ts === +// This test verifies that the checker doesn't panic when a type predicate +// references a parameter name that doesn't match any actual function parameter. + +type TypeA = { kind: 'a' }; +>TypeA : TypeA +>kind : "a" + +type TypeB = { kind: 'b' }; +>TypeB : TypeB +>kind : "b" + +type UnionType = TypeA | TypeB; +>UnionType : UnionType + +function isTypeA( +>isTypeA : (_value: UnionType) => value is TypeA + + _value: UnionType +>_value : UnionType + +): value is TypeA { // "value" doesn't match parameter "_value" + return true; +>true : true +} + +function test(input: UnionType): void { +>test : (input: UnionType) => void +>input : UnionType + + if (isTypeA(input)) { +>isTypeA(input) : boolean +>isTypeA : (_value: UnionType) => value is TypeA +>input : UnionType + + console.log(input.kind); +>console.log(input.kind) : void +>console.log : (...data: any[]) => void +>console : Console +>log : (...data: any[]) => void +>input.kind : "a" | "b" +>input : UnionType +>kind : "a" | "b" + } +} +