diff --git a/internal/checker/inference.go b/internal/checker/inference.go index d395f03a98..6f29d69354 100644 --- a/internal/checker/inference.go +++ b/internal/checker/inference.go @@ -239,6 +239,10 @@ func (c *Checker) inferFromTypes(n *InferenceState, source *Type, target *Type) case source.flags&TypeFlagsIndexedAccess != 0 && target.flags&TypeFlagsIndexedAccess != 0: c.inferFromTypes(n, source.AsIndexedAccessType().objectType, target.AsIndexedAccessType().objectType) c.inferFromTypes(n, source.AsIndexedAccessType().indexType, target.AsIndexedAccessType().indexType) + case isLiteralType(source) && target.flags&TypeFlagsIndexedAccess != 0: + // Handle reverse inference: when source is a literal type and target is T['property'], + // try to infer T based on the constraint that T['property'] = source + c.inferFromLiteralToIndexedAccess(n, source, target.AsIndexedAccessType()) case source.flags&TypeFlagsStringMapping != 0 && target.flags&TypeFlagsStringMapping != 0: if source.symbol == target.symbol { c.inferFromTypes(n, source.AsStringMappingType().target, target.AsStringMappingType().target) @@ -1605,3 +1609,69 @@ func (c *Checker) mergeInferences(target []*InferenceInfo, source []*InferenceIn } } } + +// inferFromLiteralToIndexedAccess implements a reverse inference algorithm for indexed access types. +// This function is used during type inference when a literal value is assigned to an indexed access type. +// It infers a type parameter from a literal assigned to an indexed access, e.g. T['type'] = 'Declaration'. +func (c *Checker) inferFromLiteralToIndexedAccess(n *InferenceState, source *Type, target *IndexedAccessType) { + // Only proceed if the object type is a type variable that we're inferring + objectType := target.objectType + if objectType.flags&TypeFlagsTypeVariable != 0 { + // Get the inference info for the type parameter + inference := getInferenceInfoForType(n, objectType) + if inference == nil || inference.isFixed { + return + } + + // Get the constraint of the type parameter (e.g., ASTNode) + constraint := c.getBaseConstraintOfType(inference.typeParameter) + if constraint == nil { + return + } + + // Only handle union constraints (discriminated unions) + if constraint.flags&TypeFlagsUnion == 0 { + return + } + + // Look for a union member where the indexed access type matches the source literal + indexType := target.indexType + for _, unionMember := range constraint.Types() { + // Skip sentinel type used to block string inference + if unionMember == c.blockedStringType { + continue + } + + // Try to get the type of the indexed property from this union member + memberIndexedType := c.getIndexedAccessType(unionMember, indexType) + + // Skip if we can't resolve the indexed access + if memberIndexedType == nil || c.isErrorType(memberIndexedType) { + continue + } + + // Check if this member's indexed property type matches our literal source + if c.isTypeIdenticalTo(source, memberIndexedType) { + // Found a match! Infer this union member as a candidate for the type parameter + candidate := unionMember + + if n.priority < inference.priority { + inference.candidates = nil + inference.contraCandidates = nil + inference.topLevel = true + inference.priority = n.priority + } + + if n.priority == inference.priority { + if !slices.Contains(inference.candidates, candidate) { + inference.candidates = append(inference.candidates, candidate) + clearCachedInferences(n.inferences) + } + } + + n.inferencePriority = min(n.inferencePriority, n.priority) + return + } + } + } +} diff --git a/testdata/baselines/reference/compiler/cssTreeTypeInference.js b/testdata/baselines/reference/compiler/cssTreeTypeInference.js new file mode 100644 index 0000000000..a25455200e --- /dev/null +++ b/testdata/baselines/reference/compiler/cssTreeTypeInference.js @@ -0,0 +1,72 @@ +//// [tests/cases/compiler/cssTreeTypeInference.ts] //// + +//// [cssTreeTypeInference.ts] +// Simplified reproduction of css-tree type inference issue +// https://github.com/microsoft/typescript-go/issues/1727 + +interface Declaration { + type: 'Declaration'; + property: string; + value: string; +} + +interface Rule { + type: 'Rule'; + selector: string; + children: Declaration[]; +} + +type ASTNode = Declaration | Rule; + +interface WalkOptions { + visit: T['type']; + enter(node: T): void; +} + +declare function walk(ast: ASTNode, options: WalkOptions): void; + +// Test case 1: Simple type inference +const ast: ASTNode = { + type: 'Declaration', + property: 'color', + value: 'red' +}; + +// This should infer node as Declaration type +walk(ast, { + visit: 'Declaration', + enter(node) { + console.log(node.property); // Should not error - node should be inferred as Declaration + }, +}); + +// Test case 2: More complex scenario +declare const complexAst: Rule; + +walk(complexAst, { + visit: 'Declaration', + enter(node) { + console.log(node.value); // Should infer node as Declaration + }, +}); + +//// [cssTreeTypeInference.js] +// Test case 1: Simple type inference +const ast = { + type: 'Declaration', + property: 'color', + value: 'red' +}; +// This should infer node as Declaration type +walk(ast, { + visit: 'Declaration', + enter(node) { + console.log(node.property); // Should not error - node should be inferred as Declaration + }, +}); +walk(complexAst, { + visit: 'Declaration', + enter(node) { + console.log(node.value); // Should infer node as Declaration + }, +}); diff --git a/testdata/baselines/reference/compiler/cssTreeTypeInference.symbols b/testdata/baselines/reference/compiler/cssTreeTypeInference.symbols new file mode 100644 index 0000000000..fee5c92956 --- /dev/null +++ b/testdata/baselines/reference/compiler/cssTreeTypeInference.symbols @@ -0,0 +1,128 @@ +//// [tests/cases/compiler/cssTreeTypeInference.ts] //// + +=== cssTreeTypeInference.ts === +// Simplified reproduction of css-tree type inference issue +// https://github.com/microsoft/typescript-go/issues/1727 + +interface Declaration { +>Declaration : Symbol(Declaration, Decl(cssTreeTypeInference.ts, 0, 0)) + + type: 'Declaration'; +>type : Symbol(Declaration.type, Decl(cssTreeTypeInference.ts, 3, 23)) + + property: string; +>property : Symbol(Declaration.property, Decl(cssTreeTypeInference.ts, 4, 24)) + + value: string; +>value : Symbol(Declaration.value, Decl(cssTreeTypeInference.ts, 5, 21)) +} + +interface Rule { +>Rule : Symbol(Rule, Decl(cssTreeTypeInference.ts, 7, 1)) + + type: 'Rule'; +>type : Symbol(Rule.type, Decl(cssTreeTypeInference.ts, 9, 16)) + + selector: string; +>selector : Symbol(Rule.selector, Decl(cssTreeTypeInference.ts, 10, 17)) + + children: Declaration[]; +>children : Symbol(Rule.children, Decl(cssTreeTypeInference.ts, 11, 21)) +>Declaration : Symbol(Declaration, Decl(cssTreeTypeInference.ts, 0, 0)) +} + +type ASTNode = Declaration | Rule; +>ASTNode : Symbol(ASTNode, Decl(cssTreeTypeInference.ts, 13, 1)) +>Declaration : Symbol(Declaration, Decl(cssTreeTypeInference.ts, 0, 0)) +>Rule : Symbol(Rule, Decl(cssTreeTypeInference.ts, 7, 1)) + +interface WalkOptions { +>WalkOptions : Symbol(WalkOptions, Decl(cssTreeTypeInference.ts, 15, 34)) +>T : Symbol(T, Decl(cssTreeTypeInference.ts, 17, 22)) +>ASTNode : Symbol(ASTNode, Decl(cssTreeTypeInference.ts, 13, 1)) + + visit: T['type']; +>visit : Symbol(WalkOptions.visit, Decl(cssTreeTypeInference.ts, 17, 42)) +>T : Symbol(T, Decl(cssTreeTypeInference.ts, 17, 22)) + + enter(node: T): void; +>enter : Symbol(WalkOptions.enter, Decl(cssTreeTypeInference.ts, 18, 21)) +>node : Symbol(node, Decl(cssTreeTypeInference.ts, 19, 10)) +>T : Symbol(T, Decl(cssTreeTypeInference.ts, 17, 22)) +} + +declare function walk(ast: ASTNode, options: WalkOptions): void; +>walk : Symbol(walk, Decl(cssTreeTypeInference.ts, 20, 1)) +>T : Symbol(T, Decl(cssTreeTypeInference.ts, 22, 22)) +>ASTNode : Symbol(ASTNode, Decl(cssTreeTypeInference.ts, 13, 1)) +>ast : Symbol(ast, Decl(cssTreeTypeInference.ts, 22, 41)) +>ASTNode : Symbol(ASTNode, Decl(cssTreeTypeInference.ts, 13, 1)) +>options : Symbol(options, Decl(cssTreeTypeInference.ts, 22, 54)) +>WalkOptions : Symbol(WalkOptions, Decl(cssTreeTypeInference.ts, 15, 34)) +>T : Symbol(T, Decl(cssTreeTypeInference.ts, 22, 22)) + +// Test case 1: Simple type inference +const ast: ASTNode = { +>ast : Symbol(ast, Decl(cssTreeTypeInference.ts, 25, 5)) +>ASTNode : Symbol(ASTNode, Decl(cssTreeTypeInference.ts, 13, 1)) + + type: 'Declaration', +>type : Symbol(type, Decl(cssTreeTypeInference.ts, 25, 22)) + + property: 'color', +>property : Symbol(property, Decl(cssTreeTypeInference.ts, 26, 24)) + + value: 'red' +>value : Symbol(value, Decl(cssTreeTypeInference.ts, 27, 22)) + +}; + +// This should infer node as Declaration type +walk(ast, { +>walk : Symbol(walk, Decl(cssTreeTypeInference.ts, 20, 1)) +>ast : Symbol(ast, Decl(cssTreeTypeInference.ts, 25, 5)) + + visit: 'Declaration', +>visit : Symbol(visit, Decl(cssTreeTypeInference.ts, 32, 11)) + + enter(node) { +>enter : Symbol(enter, Decl(cssTreeTypeInference.ts, 33, 25)) +>node : Symbol(node, Decl(cssTreeTypeInference.ts, 34, 10)) + + console.log(node.property); // Should not error - node should be inferred as Declaration +>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, --, --)) +>node.property : Symbol(Declaration.property, Decl(cssTreeTypeInference.ts, 4, 24)) +>node : Symbol(node, Decl(cssTreeTypeInference.ts, 34, 10)) +>property : Symbol(Declaration.property, Decl(cssTreeTypeInference.ts, 4, 24)) + + }, +}); + +// Test case 2: More complex scenario +declare const complexAst: Rule; +>complexAst : Symbol(complexAst, Decl(cssTreeTypeInference.ts, 40, 13)) +>Rule : Symbol(Rule, Decl(cssTreeTypeInference.ts, 7, 1)) + +walk(complexAst, { +>walk : Symbol(walk, Decl(cssTreeTypeInference.ts, 20, 1)) +>complexAst : Symbol(complexAst, Decl(cssTreeTypeInference.ts, 40, 13)) + + visit: 'Declaration', +>visit : Symbol(visit, Decl(cssTreeTypeInference.ts, 42, 18)) + + enter(node) { +>enter : Symbol(enter, Decl(cssTreeTypeInference.ts, 43, 25)) +>node : Symbol(node, Decl(cssTreeTypeInference.ts, 44, 10)) + + console.log(node.value); // Should infer node as Declaration +>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, --, --)) +>node.value : Symbol(Declaration.value, Decl(cssTreeTypeInference.ts, 5, 21)) +>node : Symbol(node, Decl(cssTreeTypeInference.ts, 44, 10)) +>value : Symbol(Declaration.value, Decl(cssTreeTypeInference.ts, 5, 21)) + + }, +}); diff --git a/testdata/baselines/reference/compiler/cssTreeTypeInference.types b/testdata/baselines/reference/compiler/cssTreeTypeInference.types new file mode 100644 index 0000000000..096a9bf57c --- /dev/null +++ b/testdata/baselines/reference/compiler/cssTreeTypeInference.types @@ -0,0 +1,120 @@ +//// [tests/cases/compiler/cssTreeTypeInference.ts] //// + +=== cssTreeTypeInference.ts === +// Simplified reproduction of css-tree type inference issue +// https://github.com/microsoft/typescript-go/issues/1727 + +interface Declaration { + type: 'Declaration'; +>type : "Declaration" + + property: string; +>property : string + + value: string; +>value : string +} + +interface Rule { + type: 'Rule'; +>type : "Rule" + + selector: string; +>selector : string + + children: Declaration[]; +>children : Declaration[] +} + +type ASTNode = Declaration | Rule; +>ASTNode : ASTNode + +interface WalkOptions { + visit: T['type']; +>visit : T["type"] + + enter(node: T): void; +>enter : (node: T) => void +>node : T +} + +declare function walk(ast: ASTNode, options: WalkOptions): void; +>walk : (ast: ASTNode, options: WalkOptions) => void +>ast : ASTNode +>options : WalkOptions + +// Test case 1: Simple type inference +const ast: ASTNode = { +>ast : ASTNode +>{ type: 'Declaration', property: 'color', value: 'red'} : { type: "Declaration"; property: string; value: string; } + + type: 'Declaration', +>type : "Declaration" +>'Declaration' : "Declaration" + + property: 'color', +>property : string +>'color' : "color" + + value: 'red' +>value : string +>'red' : "red" + +}; + +// This should infer node as Declaration type +walk(ast, { +>walk(ast, { visit: 'Declaration', enter(node) { console.log(node.property); // Should not error - node should be inferred as Declaration },}) : void +>walk : (ast: ASTNode, options: WalkOptions) => void +>ast : Declaration +>{ visit: 'Declaration', enter(node) { console.log(node.property); // Should not error - node should be inferred as Declaration },} : { visit: "Declaration"; enter(node: Declaration): void; } + + visit: 'Declaration', +>visit : "Declaration" +>'Declaration' : "Declaration" + + enter(node) { +>enter : (node: Declaration) => void +>node : Declaration + + console.log(node.property); // Should not error - node should be inferred as Declaration +>console.log(node.property) : void +>console.log : (...data: any[]) => void +>console : Console +>log : (...data: any[]) => void +>node.property : string +>node : Declaration +>property : string + + }, +}); + +// Test case 2: More complex scenario +declare const complexAst: Rule; +>complexAst : Rule + +walk(complexAst, { +>walk(complexAst, { visit: 'Declaration', enter(node) { console.log(node.value); // Should infer node as Declaration },}) : void +>walk : (ast: ASTNode, options: WalkOptions) => void +>complexAst : Rule +>{ visit: 'Declaration', enter(node) { console.log(node.value); // Should infer node as Declaration },} : { visit: "Declaration"; enter(node: Declaration): void; } + + visit: 'Declaration', +>visit : "Declaration" +>'Declaration' : "Declaration" + + enter(node) { +>enter : (node: Declaration) => void +>node : Declaration + + console.log(node.value); // Should infer node as Declaration +>console.log(node.value) : void +>console.log : (...data: any[]) => void +>console : Console +>log : (...data: any[]) => void +>node.value : string +>node : Declaration +>value : string + + }, +}); diff --git a/testdata/tests/cases/compiler/cssTreeTypeInference.ts b/testdata/tests/cases/compiler/cssTreeTypeInference.ts new file mode 100644 index 0000000000..d27ba9030b --- /dev/null +++ b/testdata/tests/cases/compiler/cssTreeTypeInference.ts @@ -0,0 +1,52 @@ +// @strict: true +// @module: commonjs +// @target: es2015 + +// Simplified reproduction of css-tree type inference issue +// https://github.com/microsoft/typescript-go/issues/1727 + +interface Declaration { + type: 'Declaration'; + property: string; + value: string; +} + +interface Rule { + type: 'Rule'; + selector: string; + children: Declaration[]; +} + +type ASTNode = Declaration | Rule; + +interface WalkOptions { + visit: T['type']; + enter(node: T): void; +} + +declare function walk(ast: ASTNode, options: WalkOptions): void; + +// Test case 1: Simple type inference +const ast: ASTNode = { + type: 'Declaration', + property: 'color', + value: 'red' +}; + +// This should infer node as Declaration type +walk(ast, { + visit: 'Declaration', + enter(node) { + console.log(node.property); // Should not error - node should be inferred as Declaration + }, +}); + +// Test case 2: More complex scenario +declare const complexAst: Rule; + +walk(complexAst, { + visit: 'Declaration', + enter(node) { + console.log(node.value); // Should infer node as Declaration + }, +}); \ No newline at end of file