From 237724bb85b8f05937cc73965032f01270418ae3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 6 Dec 2025 17:54:24 +0000 Subject: [PATCH 1/6] Initial plan From d02f50042df1f6f557b9875b596fc44dbd46a0b5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 6 Dec 2025 18:14:59 +0000 Subject: [PATCH 2/6] Fix completions crash in Map constructor with index bounds check Co-authored-by: DanielRosenwasser <972891+DanielRosenwasser@users.noreply.github.com> --- internal/checker/checker.go | 2 +- ...completionsInMapConstructorNoCrash_test.go | 48 +++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 internal/fourslash/tests/manual/completionsInMapConstructorNoCrash_test.go diff --git a/internal/checker/checker.go b/internal/checker/checker.go index 6a2fac5cf7..122f459c0f 100644 --- a/internal/checker/checker.go +++ b/internal/checker/checker.go @@ -28911,7 +28911,7 @@ func (c *Checker) getContextualTypeForElementExpression(t *Type, index int, leng if isTupleType(t) { // If index is before any spread element and within the fixed part of the contextual tuple type, return // the type of the contextual tuple element. - if (firstSpreadIndex < 0 || index < firstSpreadIndex) && index < t.TargetTupleType().fixedLength { + if index >= 0 && (firstSpreadIndex < 0 || index < firstSpreadIndex) && index < t.TargetTupleType().fixedLength { return c.removeMissingType(c.getTypeArguments(t)[index], t.TargetTupleType().elementInfos[index].flags&ElementFlagsOptional != 0) } // When the length is known and the index is after all spread elements we compute the offset from the element diff --git a/internal/fourslash/tests/manual/completionsInMapConstructorNoCrash_test.go b/internal/fourslash/tests/manual/completionsInMapConstructorNoCrash_test.go new file mode 100644 index 0000000000..8a88b60644 --- /dev/null +++ b/internal/fourslash/tests/manual/completionsInMapConstructorNoCrash_test.go @@ -0,0 +1,48 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + . "github.com/microsoft/typescript-go/internal/fourslash/tests/util" + "github.com/microsoft/typescript-go/internal/testutil" +) + +// Test for issue: Completions crash in call to `new Map(...)`. +// When requesting completions inside a Map constructor's array literal, +// IndexOfNode returns -1 causing a panic when trying to access array elements. +func TestCompletionsInMapConstructorNoCrash(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + + // Test completion at position /*a*/ - before the string literal + const content1 = `const m = new Map([ + [/*a*/'0', ['0', false]], +]);` + f1, done1 := fourslash.NewFourslash(t, nil /*capabilities*/, content1) + defer done1() + // Just verify that completions don't crash - accept any completion list + f1.VerifyCompletions(t, "a", &fourslash.CompletionsExpectedList{ + ItemDefaults: &fourslash.CompletionsExpectedItemDefaults{ + CommitCharacters: &DefaultCommitCharacters, + EditRange: Ignored, + }, + Items: &fourslash.CompletionsExpectedItems{}, + }) + + // Test completion at position /*b*/ - after the array literal + const content2 = `const m = new Map([ + ['0', ['0', false]]/*b*/, +]);` + f2, done2 := fourslash.NewFourslash(t, nil /*capabilities*/, content2) + defer done2() + // Just verify that completions don't crash - accept any completion list + f2.VerifyCompletions(t, "b", &fourslash.CompletionsExpectedList{ + ItemDefaults: &fourslash.CompletionsExpectedItemDefaults{ + CommitCharacters: &DefaultCommitCharacters, + EditRange: Ignored, + }, + Items: &fourslash.CompletionsExpectedItems{}, + }) +} From 5d345a2abf7588a2e88b4d8fe7c7a834e9f96c98 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 6 Dec 2025 18:18:44 +0000 Subject: [PATCH 3/6] Improve test comment to reference fix location Co-authored-by: DanielRosenwasser <972891+DanielRosenwasser@users.noreply.github.com> --- .../tests/manual/completionsInMapConstructorNoCrash_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/fourslash/tests/manual/completionsInMapConstructorNoCrash_test.go b/internal/fourslash/tests/manual/completionsInMapConstructorNoCrash_test.go index 8a88b60644..73acc7a00a 100644 --- a/internal/fourslash/tests/manual/completionsInMapConstructorNoCrash_test.go +++ b/internal/fourslash/tests/manual/completionsInMapConstructorNoCrash_test.go @@ -10,7 +10,8 @@ import ( // Test for issue: Completions crash in call to `new Map(...)`. // When requesting completions inside a Map constructor's array literal, -// IndexOfNode returns -1 causing a panic when trying to access array elements. +// IndexOfNode returns -1 causing a panic in getContextualTypeForElementExpression +// when trying to access array elements without a bounds check. func TestCompletionsInMapConstructorNoCrash(t *testing.T) { t.Parallel() From 20f8305d8736eb7bd99afca2fc8017002e5df9aa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 7 Dec 2025 02:01:01 +0000 Subject: [PATCH 4/6] Fix completions crash at language service layer with OpenBracketToken handling Co-authored-by: DanielRosenwasser <972891+DanielRosenwasser@users.noreply.github.com> --- internal/checker/checker.go | 2 +- internal/checker/services.go | 11 ++++ ...nsInArrayLiteralWithContextualType_test.go | 51 +++++++++++++++++++ internal/ls/completions.go | 31 +++++++++++ 4 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 internal/fourslash/tests/manual/completionsInArrayLiteralWithContextualType_test.go diff --git a/internal/checker/checker.go b/internal/checker/checker.go index 122f459c0f..6a2fac5cf7 100644 --- a/internal/checker/checker.go +++ b/internal/checker/checker.go @@ -28911,7 +28911,7 @@ func (c *Checker) getContextualTypeForElementExpression(t *Type, index int, leng if isTupleType(t) { // If index is before any spread element and within the fixed part of the contextual tuple type, return // the type of the contextual tuple element. - if index >= 0 && (firstSpreadIndex < 0 || index < firstSpreadIndex) && index < t.TargetTupleType().fixedLength { + if (firstSpreadIndex < 0 || index < firstSpreadIndex) && index < t.TargetTupleType().fixedLength { return c.removeMissingType(c.getTypeArguments(t)[index], t.TargetTupleType().elementInfos[index].flags&ElementFlagsOptional != 0) } // When the length is known and the index is after all spread elements we compute the offset from the element diff --git a/internal/checker/services.go b/internal/checker/services.go index cec934f547..6bc0047798 100644 --- a/internal/checker/services.go +++ b/internal/checker/services.go @@ -671,6 +671,17 @@ func (c *Checker) GetContextualDeclarationsForObjectLiteralElement(objectLiteral return result } +// GetContextualTypeForArrayElement returns the contextual type for an element at the given index +// in an array with the given contextual type. +func (c *Checker) GetContextualTypeForArrayElement(contextualArrayType *Type, elementIndex int) *Type { + if contextualArrayType == nil { + return nil + } + // Get spread indices - since we don't have access to the actual array literal, + // we'll assume no spreads for now (firstSpreadIndex = -1, lastSpreadIndex = -1) + return c.getContextualTypeForElementExpression(contextualArrayType, elementIndex, -1, -1, -1) +} + var knownGenericTypeNames = map[string]struct{}{ "Array": {}, "ArrayLike": {}, diff --git a/internal/fourslash/tests/manual/completionsInArrayLiteralWithContextualType_test.go b/internal/fourslash/tests/manual/completionsInArrayLiteralWithContextualType_test.go new file mode 100644 index 0000000000..89e53169ff --- /dev/null +++ b/internal/fourslash/tests/manual/completionsInArrayLiteralWithContextualType_test.go @@ -0,0 +1,51 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + . "github.com/microsoft/typescript-go/internal/fourslash/tests/util" + "github.com/microsoft/typescript-go/internal/testutil" +) + +// Test that string literal completions are suggested in tuple contexts +// even without typing a quote character. +func TestCompletionsInArrayLiteralWithContextualType(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + + // Test 1: Completions after `[` in a tuple should suggest string literals + const content1 = `let y: ["foo" | "bar", string] = [/*a*/];` + f1, done1 := fourslash.NewFourslash(t, nil /*capabilities*/, content1) + defer done1() + f1.VerifyCompletions(t, "a", &fourslash.CompletionsExpectedList{ + ItemDefaults: &fourslash.CompletionsExpectedItemDefaults{ + CommitCharacters: &DefaultCommitCharacters, + EditRange: Ignored, + }, + Items: &fourslash.CompletionsExpectedItems{ + Includes: []fourslash.CompletionsExpectedItem{ + "\"foo\"", + "\"bar\"", + }, + }, + }) + + // Test 2: Completions after `,` in a tuple should provide contextual type for second element + const content2 = `let z: ["a", "b" | "c"] = ["a", /*b*/];` + f2, done2 := fourslash.NewFourslash(t, nil /*capabilities*/, content2) + defer done2() + f2.VerifyCompletions(t, "b", &fourslash.CompletionsExpectedList{ + ItemDefaults: &fourslash.CompletionsExpectedItemDefaults{ + CommitCharacters: &DefaultCommitCharacters, + EditRange: Ignored, + }, + Items: &fourslash.CompletionsExpectedItems{ + Includes: []fourslash.CompletionsExpectedItem{ + "\"b\"", + "\"c\"", + }, + }, + }) +} diff --git a/internal/ls/completions.go b/internal/ls/completions.go index 905eb8e174..a7f38f4afd 100644 --- a/internal/ls/completions.go +++ b/internal/ls/completions.go @@ -2967,6 +2967,37 @@ func getContextualType(previousToken *ast.Node, position int, file *ast.SourceFi return typeChecker.GetContextualTypeForJsxAttribute(parent.Parent) } return nil + case ast.KindOpenBracketToken: + // When completing after `[` in an array literal (e.g., `[/*here*/]`), + // we should provide contextual type for the first element + if ast.IsArrayLiteralExpression(parent) { + contextualArrayType := typeChecker.GetContextualType(parent, checker.ContextFlagsNone) + if contextualArrayType != nil { + // Get the type for the first element (index 0) + return typeChecker.GetContextualTypeForArrayElement(contextualArrayType, 0) + } + } + return nil + case ast.KindCommaToken: + // When completing after `,` in an array literal (e.g., `[x, /*here*/]`), + // we should provide contextual type for the element after the comma + if ast.IsArrayLiteralExpression(parent) { + contextualArrayType := typeChecker.GetContextualType(parent, checker.ContextFlagsNone) + if contextualArrayType != nil { + // Count how many elements come before the cursor position + arrayLiteral := parent.AsArrayLiteralExpression() + elementIndex := 0 + for _, elem := range arrayLiteral.Elements.Nodes { + if elem.Pos() < position { + elementIndex++ + } else { + break + } + } + return typeChecker.GetContextualTypeForArrayElement(contextualArrayType, elementIndex) + } + } + return nil case ast.KindQuestionToken: // When completing after `?` in a ternary conditional (e.g., `foo(a ? /*here*/)`), // we need to look at the parent conditional expression to find the contextual type. From 90ca278a1b4f1fb366c5a2980d0cb1cd4c85bb69 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 7 Dec 2025 02:04:33 +0000 Subject: [PATCH 5/6] Improve comment clarity in GetContextualTypeForArrayElement Co-authored-by: DanielRosenwasser <972891+DanielRosenwasser@users.noreply.github.com> --- internal/checker/services.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/internal/checker/services.go b/internal/checker/services.go index 6bc0047798..05236b85f0 100644 --- a/internal/checker/services.go +++ b/internal/checker/services.go @@ -677,8 +677,9 @@ func (c *Checker) GetContextualTypeForArrayElement(contextualArrayType *Type, el if contextualArrayType == nil { return nil } - // Get spread indices - since we don't have access to the actual array literal, - // we'll assume no spreads for now (firstSpreadIndex = -1, lastSpreadIndex = -1) + // Pass -1 for length, firstSpreadIndex, and lastSpreadIndex since we don't have + // access to the actual array literal. This falls back to getting the iterated type + // or checking numeric properties, which is appropriate for completion contexts. return c.getContextualTypeForElementExpression(contextualArrayType, elementIndex, -1, -1, -1) } From d2f9582bbc3d2bf101160e5b028dd201fd1572a3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 9 Dec 2025 00:41:56 +0000 Subject: [PATCH 6/6] Add test case to verify -1 property edge case is fixed Co-authored-by: DanielRosenwasser <972891+DanielRosenwasser@users.noreply.github.com> --- ...onsInArrayLiteralWithContextualType_test.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/internal/fourslash/tests/manual/completionsInArrayLiteralWithContextualType_test.go b/internal/fourslash/tests/manual/completionsInArrayLiteralWithContextualType_test.go index 89e53169ff..0325115183 100644 --- a/internal/fourslash/tests/manual/completionsInArrayLiteralWithContextualType_test.go +++ b/internal/fourslash/tests/manual/completionsInArrayLiteralWithContextualType_test.go @@ -48,4 +48,22 @@ func TestCompletionsInArrayLiteralWithContextualType(t *testing.T) { }, }, }) + + // Test 3: Verify that properties named "-1" are NOT suggested in array literals + // This was a bug in the old implementation where passing -1 as an index would + // check for a property named "-1" and suggest its value + const content3 = `let x: { "-1": "hello" } = [/*c*/];` + f3, done3 := fourslash.NewFourslash(t, nil /*capabilities*/, content3) + defer done3() + f3.VerifyCompletions(t, "c", &fourslash.CompletionsExpectedList{ + ItemDefaults: &fourslash.CompletionsExpectedItemDefaults{ + CommitCharacters: &DefaultCommitCharacters, + EditRange: Ignored, + }, + Items: &fourslash.CompletionsExpectedItems{ + Excludes: []string{ + "\"hello\"", + }, + }, + }) }