Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions tsc/internal/ast/diagnostic.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,12 +240,22 @@ type DiagnosticsCollection struct {
nonFileDiagnosticsSorted bool
diagnosticIndex map[diagnosticLocationKey]*Diagnostic
diagnosticCollisions map[diagnosticLocationKey][]*Diagnostic
isStaging bool
stagingDiagnostics []*Diagnostic
Comment on lines +243 to +244
}

func (c *DiagnosticsCollection) Add(diagnostic *Diagnostic) *Diagnostic {
c.mu.Lock()
defer c.mu.Unlock()

if c.isStaging {
c.stagingDiagnostics = append(c.stagingDiagnostics, diagnostic)
return diagnostic
}
return c.addUnlocked(diagnostic)
}

func (c *DiagnosticsCollection) addUnlocked(diagnostic *Diagnostic) *Diagnostic {
key := getDiagnosticLocationKey(diagnostic)
if existing := c.diagnosticIndex[key]; existing != nil {
if EqualDiagnostics(existing, diagnostic) {
Expand Down Expand Up @@ -363,6 +373,36 @@ func (c *DiagnosticsCollection) GetDiagnostics() []*Diagnostic {
return diagnostics
}

func (c *DiagnosticsCollection) IsStaging() bool {
c.mu.Lock()
defer c.mu.Unlock()
return c.isStaging
}

func (c *DiagnosticsCollection) SetIsStaging(v bool) {
c.mu.Lock()
defer c.mu.Unlock()
c.isStaging = v
}

func (c *DiagnosticsCollection) CommitStaged() {
c.mu.Lock()
defer c.mu.Unlock()

savedIsStaging := c.isStaging
c.isStaging = false
for _, diagnostic := range c.stagingDiagnostics {
c.addUnlocked(diagnostic)
}
c.isStaging = savedIsStaging
}

func (c *DiagnosticsCollection) RevertStaged() {
c.mu.Lock()
defer c.mu.Unlock()
c.stagingDiagnostics = nil
}

func getDiagnosticPath(d *Diagnostic) string {
if d.File() != nil {
return d.File().FileName()
Expand Down
20 changes: 20 additions & 0 deletions tsc/internal/ast/utilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -3030,6 +3030,26 @@ func ForEachChildAndJSDoc(node *Node, sourceFile *SourceFile, v Visitor) bool {
return node.ForEachChild(v)
}

func ForEachChildRecursively(root *Node, visit func(*Node) bool) bool {
type queueEntry struct {
node *Node
parent *Node
}
queue := []queueEntry{{node: root, parent: nil}}
for len(queue) > 0 {
entry := queue[len(queue)-1]
queue = queue[:len(queue)-1]
if visit(entry.node) {
return true
}
entry.node.ForEachChild(func(child *Node) bool {
queue = append(queue, queueEntry{node: child, parent: entry.node})
return false
})
}
return false
}

func HasTypeArguments(node *Node) bool {
switch node.Kind {
case KindCallExpression, KindNewExpression, KindTaggedTemplateExpression,
Expand Down
80 changes: 80 additions & 0 deletions tsc/internal/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -7572,6 +7572,86 @@ func (c *Checker) reportObjectPossiblyNullOrUndefinedError(node *ast.Node, facts
}

func (c *Checker) checkExpressionWithContextualType(node *ast.Node, contextualType *Type, inferenceContext *InferenceContext, checkMode CheckMode) *Type {
if contextualType.flags&TypeFlagsTypeParameter == 0 || !c.isTypeParameterDependent(contextualType.AsTypeParameter()) || checkMode&CheckModeContextual != 0 {
return c.checkExpressionWithContextualTypeWorker(node, contextualType, inferenceContext, checkMode)
}
typeParameter := contextualType
typeParameterConstraint := c.getResolvedBaseConstraint(typeParameter, nil)
c.diagnostics.SetIsStaging(true)
c.suggestionDiagnostics.SetIsStaging(true)
valueType := c.checkExpressionWithContextualTypeWorker(node, typeParameter, inferenceContext, checkMode)
var previousValueType *Type
passes := 0
for {
if previousValueType != nil && c.isTypeIdenticalTo(valueType, previousValueType) {
c.commitStagedDiagnostics()
return valueType
}
if passes >= 5 {
c.commitStagedDiagnostics()
c.error(node, diagnostics.Dependent_contextual_inference_requires_too_many_passes_and_possibly_infinite)
return valueType
Comment on lines +7590 to +7593
}

contextualType = c.cloneTypeParameter(typeParameter)
contextualType.AsConstrainedType().resolvedBaseConstraint = c.instantiateType(
typeParameterConstraint,
newSimpleTypeMapper(typeParameter, valueType),
)

c.resetNodeCheck(node)
c.revertStagedDiagnostics()
previousValueType = valueType
valueType = c.checkExpressionWithContextualTypeWorker(node, contextualType, inferenceContext, checkMode & ^CheckModeSkipContextSensitive)
passes++
}
}

func (c *Checker) isTypeParameterDependent(t *TypeParameter) bool {
constraintNode := c.getConstraintDeclaration(t.AsType())
if constraintNode == nil {
return false
}
return ast.ForEachChildRecursively(constraintNode, func(child *ast.Node) bool {
return ast.IsTypeReferenceNode(child) && c.getSymbolFromTypeReference(child) == t.symbol
})
}

func (c *Checker) commitStagedDiagnostics() {
c.diagnostics.CommitStaged()
c.suggestionDiagnostics.CommitStaged()
c.diagnostics.SetIsStaging(false)
c.suggestionDiagnostics.SetIsStaging(false)
}

func (c *Checker) revertStagedDiagnostics() {
c.diagnostics.RevertStaged()
c.suggestionDiagnostics.RevertStaged()
}

func (c *Checker) resetNodeCheck(node *ast.Node) {
ast.ForEachChildRecursively(node, func(child *ast.Node) bool {
if links := c.nodeLinks.TryGet(child); links != nil {
links.flags &^= NodeCheckFlagsTypeChecked | NodeCheckFlagsContextChecked
}
if typeLinks := c.typeNodeLinks.TryGet(child); typeLinks != nil {
typeLinks.resolvedType = nil
}
if signatureLinks := c.signatureLinks.TryGet(child); signatureLinks != nil {
signatureLinks.resolvedSignature = nil
}
delete(c.contextFreeTypes, child)
if symbol := child.Symbol(); symbol != nil {
if valueLinks := c.valueSymbolLinks.TryGet(symbol); valueLinks != nil {
valueLinks.resolvedType = nil
valueLinks.writeType = nil
}
}
return false
})
}

func (c *Checker) checkExpressionWithContextualTypeWorker(node *ast.Node, contextualType *Type, inferenceContext *InferenceContext, checkMode CheckMode) *Type {
contextNode := c.getContextNode(node)
c.pushContextualType(contextNode, contextualType, false /*isCache*/)
c.pushInferenceContext(contextNode, inferenceContext)
Expand Down
4 changes: 4 additions & 0 deletions tsc/internal/diagnostics/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -8359,6 +8359,10 @@
"category": "Message",
"code": 95197
},
"Dependent contextual inference requires too many passes and possibly infinite": {
"category": "Error",
"code": 95198
},

"No value exists in scope for the shorthand property '{0}'. Either declare one or provide an initializer.": {
"category": "Error",
Expand Down
4 changes: 4 additions & 0 deletions tsc/internal/diagnostics/diagnostics_generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file modified tsc/internal/diagnostics/loc/cs-CZ.json.gz
Binary file not shown.
Binary file modified tsc/internal/diagnostics/loc/de-DE.json.gz
Binary file not shown.
Binary file modified tsc/internal/diagnostics/loc/es-ES.json.gz
Binary file not shown.
Binary file modified tsc/internal/diagnostics/loc/fr-FR.json.gz
Binary file not shown.
Binary file modified tsc/internal/diagnostics/loc/it-IT.json.gz
Binary file not shown.
Binary file modified tsc/internal/diagnostics/loc/ja-JP.json.gz
Binary file not shown.
Binary file modified tsc/internal/diagnostics/loc/ko-KR.json.gz
Binary file not shown.
Binary file modified tsc/internal/diagnostics/loc/pl-PL.json.gz
Binary file not shown.
Binary file modified tsc/internal/diagnostics/loc/pt-BR.json.gz
Binary file not shown.
Binary file modified tsc/internal/diagnostics/loc/ru-RU.json.gz
Binary file not shown.
Binary file modified tsc/internal/diagnostics/loc/tr-TR.json.gz
Binary file not shown.
Binary file modified tsc/internal/diagnostics/loc/zh-CN.json.gz
Binary file not shown.
Binary file modified tsc/internal/diagnostics/loc/zh-TW.json.gz
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ declare function foo2<T extends { [P in keyof T & string as Capitalize<P>]: V },
>a : T

export const r2 = foo2({A: "a"});
>r2 : { A: string; }
>foo2({A: "a"}) : { A: string; }
>r2 : { A: "a"; }
>foo2({A: "a"}) : { A: "a"; }
>foo2 : <T extends { [P in keyof T & string as Capitalize<P>]: V; }, V extends string>(a: T) => T
>{A: "a"} : { A: string; }
>A : string
>{A: "a"} : { A: "a"; }
>A : "a"
>"a" : "a"

Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//// [tests/cases/compiler/dependentContextualInferenceAiSdk.ts] ////

//// [dependentContextualInferenceAiSdk.ts]
declare const streamText:
<T extends {
tools: {
[K in keyof T["tools"]]: {
inputSchema: { "~type": unknown },
execute: (input: T["tools"][K]["inputSchema"]["~type"]) => unknown
}
},
}> (t: T) => {}

declare const z:
{ object:
<T extends Record<string, { "~type": unknown }>>(t: T) =>
{ "~type": { [K in keyof T]: T[K]["~type"] } }
, string: () => { "~type": string }
}

streamText({
tools: {
getWeather: {
inputSchema: z.object({ location: z.string() }),
execute: input => {
const _check: { location: string } = input
return "whatever"
}
}
},
})


//// [dependentContextualInferenceAiSdk.js]
"use strict";
streamText({
tools: {
getWeather: {
inputSchema: z.object({ location: z.string() }),
execute: input => {
const _check = input;
return "whatever";
}
}
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
//// [tests/cases/compiler/dependentContextualInferenceAiSdk.ts] ////

=== dependentContextualInferenceAiSdk.ts ===
declare const streamText:
>streamText : Symbol(streamText, Decl(dependentContextualInferenceAiSdk.ts, 0, 13))

<T extends {
>T : Symbol(T, Decl(dependentContextualInferenceAiSdk.ts, 1, 3))

tools: {
>tools : Symbol(tools, Decl(dependentContextualInferenceAiSdk.ts, 1, 14))

[K in keyof T["tools"]]: {
>K : Symbol(K, Decl(dependentContextualInferenceAiSdk.ts, 3, 7))
>T : Symbol(T, Decl(dependentContextualInferenceAiSdk.ts, 1, 3))

inputSchema: { "~type": unknown },
>inputSchema : Symbol(inputSchema, Decl(dependentContextualInferenceAiSdk.ts, 3, 32))
>"~type" : Symbol("~type", Decl(dependentContextualInferenceAiSdk.ts, 4, 22))

execute: (input: T["tools"][K]["inputSchema"]["~type"]) => unknown
>execute : Symbol(execute, Decl(dependentContextualInferenceAiSdk.ts, 4, 42))
>input : Symbol(input, Decl(dependentContextualInferenceAiSdk.ts, 5, 18))
>T : Symbol(T, Decl(dependentContextualInferenceAiSdk.ts, 1, 3))
>K : Symbol(K, Decl(dependentContextualInferenceAiSdk.ts, 3, 7))
}
},
}> (t: T) => {}
>t : Symbol(t, Decl(dependentContextualInferenceAiSdk.ts, 8, 6))
>T : Symbol(T, Decl(dependentContextualInferenceAiSdk.ts, 1, 3))

declare const z:
>z : Symbol(z, Decl(dependentContextualInferenceAiSdk.ts, 10, 13))

{ object:
>object : Symbol(object, Decl(dependentContextualInferenceAiSdk.ts, 11, 3))

<T extends Record<string, { "~type": unknown }>>(t: T) =>
>T : Symbol(T, Decl(dependentContextualInferenceAiSdk.ts, 12, 7))
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
>"~type" : Symbol("~type", Decl(dependentContextualInferenceAiSdk.ts, 12, 33))
>t : Symbol(t, Decl(dependentContextualInferenceAiSdk.ts, 12, 55))
>T : Symbol(T, Decl(dependentContextualInferenceAiSdk.ts, 12, 7))

{ "~type": { [K in keyof T]: T[K]["~type"] } }
>"~type" : Symbol("~type", Decl(dependentContextualInferenceAiSdk.ts, 13, 9))
>K : Symbol(K, Decl(dependentContextualInferenceAiSdk.ts, 13, 22))
>T : Symbol(T, Decl(dependentContextualInferenceAiSdk.ts, 12, 7))
>T : Symbol(T, Decl(dependentContextualInferenceAiSdk.ts, 12, 7))
>K : Symbol(K, Decl(dependentContextualInferenceAiSdk.ts, 13, 22))

, string: () => { "~type": string }
>string : Symbol(string, Decl(dependentContextualInferenceAiSdk.ts, 14, 3))
>"~type" : Symbol("~type", Decl(dependentContextualInferenceAiSdk.ts, 14, 19))
}

streamText({
>streamText : Symbol(streamText, Decl(dependentContextualInferenceAiSdk.ts, 0, 13))

tools: {
>tools : Symbol(tools, Decl(dependentContextualInferenceAiSdk.ts, 17, 12))

getWeather: {
>getWeather : Symbol(getWeather, Decl(dependentContextualInferenceAiSdk.ts, 18, 10))

inputSchema: z.object({ location: z.string() }),
>inputSchema : Symbol(inputSchema, Decl(dependentContextualInferenceAiSdk.ts, 19, 17))
>z.object : Symbol(object, Decl(dependentContextualInferenceAiSdk.ts, 11, 3))
>z : Symbol(z, Decl(dependentContextualInferenceAiSdk.ts, 10, 13))
>object : Symbol(object, Decl(dependentContextualInferenceAiSdk.ts, 11, 3))
>location : Symbol(location, Decl(dependentContextualInferenceAiSdk.ts, 20, 29))
>z.string : Symbol(string, Decl(dependentContextualInferenceAiSdk.ts, 14, 3))
>z : Symbol(z, Decl(dependentContextualInferenceAiSdk.ts, 10, 13))
>string : Symbol(string, Decl(dependentContextualInferenceAiSdk.ts, 14, 3))

execute: input => {
>execute : Symbol(execute, Decl(dependentContextualInferenceAiSdk.ts, 20, 54))
>input : Symbol(input, Decl(dependentContextualInferenceAiSdk.ts, 21, 14))

const _check: { location: string } = input
>_check : Symbol(_check, Decl(dependentContextualInferenceAiSdk.ts, 22, 13))
>location : Symbol(location, Decl(dependentContextualInferenceAiSdk.ts, 22, 23))
>input : Symbol(input, Decl(dependentContextualInferenceAiSdk.ts, 21, 14))

return "whatever"
}
}
},
})

Loading