Skip to content

Commit 1d27d14

Browse files
Fix panic on type predicate parameter mismatch with bounds checking
Co-authored-by: RyanCavanaugh <6685088+RyanCavanaugh@users.noreply.github.com>
1 parent 5e5c7f2 commit 1d27d14

File tree

2 files changed

+67
-2
lines changed

2 files changed

+67
-2
lines changed

internal/checker/checker_test.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,71 @@ func TestCheckSrcCompiler(t *testing.T) {
8080
p.CheckSourceFiles(t.Context(), nil)
8181
}
8282

83+
func TestTypePredicateParameterMismatch(t *testing.T) {
84+
t.Parallel()
85+
86+
// This test verifies that the checker doesn't panic when a type predicate
87+
// references a parameter name that doesn't match any actual function parameter.
88+
// The issue was that getTypePredicateArgument would try to access the arguments
89+
// array with a negative index (-1) when the parameter name wasn't found.
90+
content := `type TypeA = { kind: 'a' };
91+
type TypeB = { kind: 'b' };
92+
type UnionType = TypeA | TypeB;
93+
94+
function isTypeA(
95+
_value: UnionType
96+
): value is TypeA { // "value" doesn't match parameter "_value"
97+
return true;
98+
}
99+
100+
function test(input: UnionType): void {
101+
if (isTypeA(input)) {
102+
console.log(input.kind);
103+
}
104+
}`
105+
fs := vfstest.FromMap(map[string]string{
106+
"/test.ts": content,
107+
"/tsconfig.json": `
108+
{
109+
"compilerOptions": {
110+
"target": "es2015",
111+
"module": "commonjs",
112+
"strict": true
113+
},
114+
"files": ["test.ts"]
115+
}
116+
`,
117+
}, false /*useCaseSensitiveFileNames*/)
118+
fs = bundled.WrapFS(fs)
119+
120+
cd := "/"
121+
host := compiler.NewCompilerHost(cd, fs, bundled.LibPath(), nil, nil)
122+
123+
parsed, errors := tsoptions.GetParsedCommandLineOfConfigFile("/tsconfig.json", &core.CompilerOptions{}, host, nil)
124+
assert.Equal(t, len(errors), 0, "Expected no errors in parsed command line")
125+
126+
p := compiler.NewProgram(compiler.ProgramOptions{
127+
Config: parsed,
128+
Host: host,
129+
})
130+
131+
// This should not panic - it should report a diagnostic error instead
132+
diags := p.GetSemanticDiagnostics(t.Context(), nil)
133+
134+
// We expect at least one diagnostic error (TS1225: Cannot find parameter 'value')
135+
assert.Assert(t, len(diags) > 0, "Expected at least one diagnostic error")
136+
137+
// Verify the expected error code is present
138+
foundExpectedError := false
139+
for _, diag := range diags {
140+
if diag.Code() == 1225 { // TS1225: Cannot find parameter
141+
foundExpectedError = true
142+
break
143+
}
144+
}
145+
assert.Assert(t, foundExpectedError, "Expected to find error TS1225 (Cannot find parameter)")
146+
}
147+
83148
func BenchmarkNewChecker(b *testing.B) {
84149
repo.SkipIfNoTypeScriptSubmodule(b)
85150
fs := osvfs.FS()

internal/checker/flow.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2405,7 +2405,7 @@ func (c *Checker) typeMaybeAssignableTo(source *Type, target *Type) bool {
24052405
func (c *Checker) getTypePredicateArgument(predicate *TypePredicate, callExpression *ast.Node) *ast.Node {
24062406
if predicate.kind == TypePredicateKindIdentifier || predicate.kind == TypePredicateKindAssertsIdentifier {
24072407
arguments := callExpression.Arguments()
2408-
if int(predicate.parameterIndex) < len(arguments) {
2408+
if predicate.parameterIndex >= 0 && int(predicate.parameterIndex) < len(arguments) {
24092409
return arguments[predicate.parameterIndex]
24102410
}
24112411
} else {
@@ -2496,7 +2496,7 @@ func (c *Checker) isReachableFlowNodeWorker(f *FlowState, flow *ast.FlowNode, no
24962496
case flags&ast.FlowFlagsCall != 0:
24972497
if signature := c.getEffectsSignature(flow.Node); signature != nil {
24982498
if predicate := c.getTypePredicateOfSignature(signature); predicate != nil && predicate.kind == TypePredicateKindAssertsIdentifier && predicate.t == nil {
2499-
if arguments := flow.Node.Arguments(); int(predicate.parameterIndex) < len(arguments) && c.isFalseExpression(arguments[predicate.parameterIndex]) {
2499+
if arguments := flow.Node.Arguments(); predicate.parameterIndex >= 0 && int(predicate.parameterIndex) < len(arguments) && c.isFalseExpression(arguments[predicate.parameterIndex]) {
25002500
return false
25012501
}
25022502
}

0 commit comments

Comments
 (0)