From cb560f05e3cfe19cbdedd3eb661b8f58a06137f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Mon, 23 Oct 2023 13:34:24 +0200 Subject: [PATCH 1/4] Filter out classes with private members from the contextual types for object literal nodes --- src/compiler/checker.ts | 81 ++++++++++++------- src/services/completions.ts | 3 +- ...ionIncludesClassWithPrivateMember1.symbols | 39 +++++++++ ...itionIncludesClassWithPrivateMember1.types | 39 +++++++++ ...ionIncludesClassWithPrivateMember2.symbols | 39 +++++++++ ...itionIncludesClassWithPrivateMember2.types | 39 +++++++++ ...PositionIncludesClassWithPrivateMember1.ts | 18 +++++ ...PositionIncludesClassWithPrivateMember2.ts | 17 ++++ ...PositionIncludesClassWithPrivateMember1.ts | 37 +++++++++ ...PositionIncludesClassWithPrivateMember2.ts | 37 +++++++++ ...PositionIncludesClassWithPrivateMember3.ts | 37 +++++++++ ...PositionIncludesClassWithPrivateMember4.ts | 37 +++++++++ 12 files changed, 392 insertions(+), 31 deletions(-) create mode 100644 tests/baselines/reference/contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.symbols create mode 100644 tests/baselines/reference/contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.types create mode 100644 tests/baselines/reference/contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.symbols create mode 100644 tests/baselines/reference/contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.types create mode 100644 tests/cases/compiler/contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.ts create mode 100644 tests/cases/compiler/contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.ts create mode 100644 tests/cases/fourslash/completionsObjectLiteralPositionIncludesClassWithPrivateMember1.ts create mode 100644 tests/cases/fourslash/completionsObjectLiteralPositionIncludesClassWithPrivateMember2.ts create mode 100644 tests/cases/fourslash/completionsObjectLiteralPositionIncludesClassWithPrivateMember3.ts create mode 100644 tests/cases/fourslash/completionsObjectLiteralPositionIncludesClassWithPrivateMember4.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 69933033d3929..5e7c72a3b7d0b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -30204,6 +30204,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { ); } + function filterContextualTypeForLiteralExpressionOfObject(node: Node, contextualType: Type) { + if (!isLiteralExpressionOfObject(node)) { + return contextualType; + } + return filterType(contextualType, t => { + if (!t.symbol || !(t.symbol.flags & SymbolFlags.Class)) { + return true; + } + return every(getPropertiesOfType(t), p => !p.valueDeclaration || !isNamedDeclaration(p.valueDeclaration) || (!isPrivateIdentifier(p.valueDeclaration.name) && !(getDeclarationModifierFlagsFromSymbol(p) & ModifierFlags.NonPublicAccessibilityModifier))); + }); + } + // Return the contextual type for a given expression node. During overload resolution, a contextual type may temporarily // be "pushed" onto a node using the contextualType property. function getApparentTypeOfContextualType(node: Expression | MethodDeclaration, contextFlags: ContextFlags | undefined): Type | undefined { @@ -30270,34 +30282,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return type; } - /** - * Whoa! Do you really want to use this function? - * - * Unless you're trying to get the *non-apparent* type for a - * value-literal type or you're authoring relevant portions of this algorithm, - * you probably meant to use 'getApparentTypeOfContextualType'. - * Otherwise this may not be very useful. - * - * In cases where you *are* working on this function, you should understand - * when it is appropriate to use 'getContextualType' and 'getApparentTypeOfContextualType'. - * - * - Use 'getContextualType' when you are simply going to propagate the result to the expression. - * - Use 'getApparentTypeOfContextualType' when you're going to need the members of the type. - * - * @param node the expression whose contextual type will be returned. - * @returns the contextual type of an expression. - */ - function getContextualType(node: Expression, contextFlags: ContextFlags | undefined): Type | undefined { - if (node.flags & NodeFlags.InWithStatement) { - // We cannot answer semantic questions within a with block, do not proceed any further - return undefined; - } - // Cached contextual types are obtained with no ContextFlags, so we can only consult them for - // requests with no ContextFlags. - const index = findContextualNode(node, /*includeCaches*/ !contextFlags); - if (index >= 0) { - return contextualTypes[index]; - } + function getContextualTypeFromParent(node: Expression, contextFlags: ContextFlags | undefined): Type | undefined { const { parent } = node; switch (parent.kind) { case SyntaxKind.VariableDeclaration: @@ -30371,6 +30356,41 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return undefined; } + /** + * Whoa! Do you really want to use this function? + * + * Unless you're trying to get the *non-apparent* type for a + * value-literal type or you're authoring relevant portions of this algorithm, + * you probably meant to use 'getApparentTypeOfContextualType'. + * Otherwise this may not be very useful. + * + * In cases where you *are* working on this function, you should understand + * when it is appropriate to use 'getContextualType' and 'getApparentTypeOfContextualType'. + * + * - Use 'getContextualType' when you are simply going to propagate the result to the expression. + * - Use 'getApparentTypeOfContextualType' when you're going to need the members of the type. + * + * @param node the expression whose contextual type will be returned. + * @returns the contextual type of an expression. + */ + function getContextualType(node: Expression, contextFlags: ContextFlags | undefined): Type | undefined { + if (node.flags & NodeFlags.InWithStatement) { + // We cannot answer semantic questions within a with block, do not proceed any further + return undefined; + } + // Cached contextual types are obtained with no ContextFlags, so we can only consult them for + // requests with no ContextFlags. + const index = findContextualNode(node, /*includeCaches*/ !contextFlags); + if (index >= 0) { + return contextualTypes[index]; + } + const contextualType = getContextualTypeFromParent(node, contextFlags); + if (!contextualType) { + return undefined; + } + return filterContextualTypeForLiteralExpressionOfObject(node, contextualType); + } + function pushCachedContextualType(node: Expression) { pushContextualType(node, getContextualType(node, /*contextFlags*/ undefined), /*isCache*/ true); } @@ -38416,8 +38436,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function checkExpressionWithContextualType(node: Expression, contextualType: Type, inferenceContext: InferenceContext | undefined, checkMode: CheckMode): Type { + const filteredContextualType = filterContextualTypeForLiteralExpressionOfObject(node, contextualType); const contextNode = getContextNode(node); - pushContextualType(contextNode, contextualType, /*isCache*/ false); + pushContextualType(contextNode, filteredContextualType, /*isCache*/ false); pushInferenceContext(contextNode, inferenceContext); const type = checkExpression(node, checkMode | CheckMode.Contextual | (inferenceContext ? CheckMode.Inferential : 0)); // In CheckMode.Inferential we collect intra-expression inference sites to process before fixing any type @@ -38428,7 +38449,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // We strip literal freshness when an appropriate contextual type is present such that contextually typed // literals always preserve their literal types (otherwise they might widen during type inference). An alternative // here would be to not mark contextually typed literals as fresh in the first place. - const result = maybeTypeOfKind(type, TypeFlags.Literal) && isLiteralOfContextualType(type, instantiateContextualType(contextualType, node, /*contextFlags*/ undefined)) ? + const result = maybeTypeOfKind(type, TypeFlags.Literal) && isLiteralOfContextualType(type, instantiateContextualType(filteredContextualType, node, /*contextFlags*/ undefined)) ? getRegularTypeOfLiteralType(type) : type; popInferenceContext(); popContextualType(); diff --git a/src/services/completions.ts b/src/services/completions.ts index d1f8bd04d4094..2208168135450 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -205,6 +205,7 @@ import { isModifier, isModifierKind, isModuleDeclaration, + isNamedDeclaration, isNamedExports, isNamedImports, isNamedImportsOrExports, @@ -5384,7 +5385,7 @@ function getApparentProperties(type: Type, node: ObjectLiteralExpression | JsxAt } function containsNonPublicProperties(props: Symbol[]) { - return some(props, p => !!(getDeclarationModifierFlagsFromSymbol(p) & ModifierFlags.NonPublicAccessibilityModifier)); + return some(props, p => !!(getDeclarationModifierFlagsFromSymbol(p) & ModifierFlags.NonPublicAccessibilityModifier) || !!p.valueDeclaration && isNamedDeclaration(p.valueDeclaration) && isPrivateIdentifier(p.valueDeclaration.name)); } /** diff --git a/tests/baselines/reference/contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.symbols b/tests/baselines/reference/contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.symbols new file mode 100644 index 0000000000000..0a5d5c53645cd --- /dev/null +++ b/tests/baselines/reference/contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.symbols @@ -0,0 +1,39 @@ +//// [tests/cases/compiler/contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.ts] //// + +=== contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.ts === +class Foo { +>Foo : Symbol(Foo, Decl(contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.ts, 0, 0)) + + #foo = "foo"; +>#foo : Symbol(Foo.#foo, Decl(contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.ts, 0, 11)) + + doStuff(cb: (arg: string) => void) {} +>doStuff : Symbol(Foo.doStuff, Decl(contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.ts, 1, 15)) +>cb : Symbol(cb, Decl(contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.ts, 2, 10)) +>arg : Symbol(arg, Decl(contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.ts, 2, 15)) +} + +interface FooLike { +>FooLike : Symbol(FooLike, Decl(contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.ts, 3, 1)) + + doStuff: (cb: (arg: number) => void) => void; +>doStuff : Symbol(FooLike.doStuff, Decl(contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.ts, 5, 19)) +>cb : Symbol(cb, Decl(contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.ts, 6, 12)) +>arg : Symbol(arg, Decl(contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.ts, 6, 17)) +} + +declare function useIt(arg: Foo | FooLike): void; +>useIt : Symbol(useIt, Decl(contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.ts, 7, 1)) +>arg : Symbol(arg, Decl(contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.ts, 9, 23)) +>Foo : Symbol(Foo, Decl(contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.ts, 0, 0)) +>FooLike : Symbol(FooLike, Decl(contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.ts, 3, 1)) + +useIt({ +>useIt : Symbol(useIt, Decl(contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.ts, 7, 1)) + + doStuff: (arg) => {}, +>doStuff : Symbol(doStuff, Decl(contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.ts, 11, 7)) +>arg : Symbol(arg, Decl(contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.ts, 12, 12)) + +}); + diff --git a/tests/baselines/reference/contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.types b/tests/baselines/reference/contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.types new file mode 100644 index 0000000000000..334ea64185af2 --- /dev/null +++ b/tests/baselines/reference/contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.types @@ -0,0 +1,39 @@ +//// [tests/cases/compiler/contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.ts] //// + +=== contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.ts === +class Foo { +>Foo : Foo + + #foo = "foo"; +>#foo : string +>"foo" : "foo" + + doStuff(cb: (arg: string) => void) {} +>doStuff : (cb: (arg: string) => void) => void +>cb : (arg: string) => void +>arg : string +} + +interface FooLike { + doStuff: (cb: (arg: number) => void) => void; +>doStuff : (cb: (arg: number) => void) => void +>cb : (arg: number) => void +>arg : number +} + +declare function useIt(arg: Foo | FooLike): void; +>useIt : (arg: Foo | FooLike) => void +>arg : Foo | FooLike + +useIt({ +>useIt({ doStuff: (arg) => {},}) : void +>useIt : (arg: Foo | FooLike) => void +>{ doStuff: (arg) => {},} : { doStuff: (arg: (arg: number) => void) => void; } + + doStuff: (arg) => {}, +>doStuff : (arg: (arg: number) => void) => void +>(arg) => {} : (arg: (arg: number) => void) => void +>arg : (arg: number) => void + +}); + diff --git a/tests/baselines/reference/contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.symbols b/tests/baselines/reference/contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.symbols new file mode 100644 index 0000000000000..a56f2460a4caf --- /dev/null +++ b/tests/baselines/reference/contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.symbols @@ -0,0 +1,39 @@ +//// [tests/cases/compiler/contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.ts] //// + +=== contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.ts === +class Foo { +>Foo : Symbol(Foo, Decl(contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.ts, 0, 0)) + + private foo = "foo"; +>foo : Symbol(Foo.foo, Decl(contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.ts, 0, 11)) + + doStuff(cb: (arg: string) => void) {} +>doStuff : Symbol(Foo.doStuff, Decl(contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.ts, 1, 22)) +>cb : Symbol(cb, Decl(contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.ts, 2, 10)) +>arg : Symbol(arg, Decl(contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.ts, 2, 15)) +} + +interface FooLike { +>FooLike : Symbol(FooLike, Decl(contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.ts, 3, 1)) + + doStuff: (cb: (arg: number) => void) => void; +>doStuff : Symbol(FooLike.doStuff, Decl(contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.ts, 5, 19)) +>cb : Symbol(cb, Decl(contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.ts, 6, 12)) +>arg : Symbol(arg, Decl(contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.ts, 6, 17)) +} + +declare function useIt(arg: Foo | FooLike): void; +>useIt : Symbol(useIt, Decl(contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.ts, 7, 1)) +>arg : Symbol(arg, Decl(contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.ts, 9, 23)) +>Foo : Symbol(Foo, Decl(contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.ts, 0, 0)) +>FooLike : Symbol(FooLike, Decl(contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.ts, 3, 1)) + +useIt({ +>useIt : Symbol(useIt, Decl(contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.ts, 7, 1)) + + doStuff: (arg) => {}, +>doStuff : Symbol(doStuff, Decl(contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.ts, 11, 7)) +>arg : Symbol(arg, Decl(contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.ts, 12, 12)) + +}); + diff --git a/tests/baselines/reference/contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.types b/tests/baselines/reference/contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.types new file mode 100644 index 0000000000000..4d141ec121069 --- /dev/null +++ b/tests/baselines/reference/contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.types @@ -0,0 +1,39 @@ +//// [tests/cases/compiler/contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.ts] //// + +=== contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.ts === +class Foo { +>Foo : Foo + + private foo = "foo"; +>foo : string +>"foo" : "foo" + + doStuff(cb: (arg: string) => void) {} +>doStuff : (cb: (arg: string) => void) => void +>cb : (arg: string) => void +>arg : string +} + +interface FooLike { + doStuff: (cb: (arg: number) => void) => void; +>doStuff : (cb: (arg: number) => void) => void +>cb : (arg: number) => void +>arg : number +} + +declare function useIt(arg: Foo | FooLike): void; +>useIt : (arg: Foo | FooLike) => void +>arg : Foo | FooLike + +useIt({ +>useIt({ doStuff: (arg) => {},}) : void +>useIt : (arg: Foo | FooLike) => void +>{ doStuff: (arg) => {},} : { doStuff: (arg: (arg: number) => void) => void; } + + doStuff: (arg) => {}, +>doStuff : (arg: (arg: number) => void) => void +>(arg) => {} : (arg: (arg: number) => void) => void +>arg : (arg: number) => void + +}); + diff --git a/tests/cases/compiler/contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.ts b/tests/cases/compiler/contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.ts new file mode 100644 index 0000000000000..5d5f6f1bbba1d --- /dev/null +++ b/tests/cases/compiler/contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.ts @@ -0,0 +1,18 @@ +// @strict: true +// @target: esnext +// @noEmit: true + +class Foo { + #foo = "foo"; + doStuff(cb: (arg: string) => void) {} +} + +interface FooLike { + doStuff: (cb: (arg: number) => void) => void; +} + +declare function useIt(arg: Foo | FooLike): void; + +useIt({ + doStuff: (arg) => {}, +}); diff --git a/tests/cases/compiler/contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.ts b/tests/cases/compiler/contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.ts new file mode 100644 index 0000000000000..ab7432439fde3 --- /dev/null +++ b/tests/cases/compiler/contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.ts @@ -0,0 +1,17 @@ +// @strict: true +// @noEmit: true + +class Foo { + private foo = "foo"; + doStuff(cb: (arg: string) => void) {} +} + +interface FooLike { + doStuff: (cb: (arg: number) => void) => void; +} + +declare function useIt(arg: Foo | FooLike): void; + +useIt({ + doStuff: (arg) => {}, +}); diff --git a/tests/cases/fourslash/completionsObjectLiteralPositionIncludesClassWithPrivateMember1.ts b/tests/cases/fourslash/completionsObjectLiteralPositionIncludesClassWithPrivateMember1.ts new file mode 100644 index 0000000000000..ce7898246075e --- /dev/null +++ b/tests/cases/fourslash/completionsObjectLiteralPositionIncludesClassWithPrivateMember1.ts @@ -0,0 +1,37 @@ +/// + +// @strict: true + +//// type ExampleInit = { +//// foo: number; +//// }; +//// +//// type ExampleLike = Example | ExampleInit; +//// +//// class Example { +//// static isExample(value: any): value is Example { +//// return #foo in value; +//// } +//// +//// static from(exampleLike: ExampleLike): Example { +//// if (Example.isExample(exampleLike)) { +//// return exampleLike; +//// } +//// return new Example(exampleLike); +//// } +//// +//// readonly #foo: number; +//// +//// constructor({ foo }: ExampleInit) { +//// this.#foo = foo; +//// } +//// } +//// +//// const example = Example.from({ +//// /*1*/ +//// }); + +verify.completions({ + marker: "1", + exact: ["foo"] +}) diff --git a/tests/cases/fourslash/completionsObjectLiteralPositionIncludesClassWithPrivateMember2.ts b/tests/cases/fourslash/completionsObjectLiteralPositionIncludesClassWithPrivateMember2.ts new file mode 100644 index 0000000000000..47a91fc3b1d41 --- /dev/null +++ b/tests/cases/fourslash/completionsObjectLiteralPositionIncludesClassWithPrivateMember2.ts @@ -0,0 +1,37 @@ +/// + +// @strict: true + +//// type ExampleInit = { +//// foo: number; +//// }; +//// +//// type ExampleLike = Example | ExampleInit; +//// +//// class Example { +//// static isExample(value: any): value is Example { +//// return #foo in value; +//// } +//// +//// static from({ exampleLike }: { exampleLike: ExampleLike }): Example { +//// if (Example.isExample(exampleLike)) { +//// return exampleLike; +//// } +//// return new Example(exampleLike); +//// } +//// +//// readonly #foo: number; +//// +//// constructor({ foo }: ExampleInit) { +//// this.#foo = foo; +//// } +//// } +//// +//// const example = Example.from({ +//// exampleLike: { /*1*/ }, +//// }); + +verify.completions({ + marker: "1", + exact: ["foo"] +}) diff --git a/tests/cases/fourslash/completionsObjectLiteralPositionIncludesClassWithPrivateMember3.ts b/tests/cases/fourslash/completionsObjectLiteralPositionIncludesClassWithPrivateMember3.ts new file mode 100644 index 0000000000000..8c178d3f4cd9a --- /dev/null +++ b/tests/cases/fourslash/completionsObjectLiteralPositionIncludesClassWithPrivateMember3.ts @@ -0,0 +1,37 @@ +/// + +// @strict: true + +//// type ExampleInit = { +//// foo: number; +//// }; +//// +//// type ExampleLike = Example | ExampleInit; +//// +//// class Example { +//// static isExample(value: any): value is Example { +//// return 'bar' in value; +//// } +//// +//// static from(exampleLike: ExampleLike): Example { +//// if (Example.isExample(exampleLike)) { +//// return exampleLike; +//// } +//// return new Example(exampleLike); +//// } +//// +//// private bar: number; +//// +//// constructor({ foo }: ExampleInit) { +//// this.bar = foo; +//// } +//// } +//// +//// const example = Example.from({ +//// /*1*/ +//// }); + +verify.completions({ + marker: "1", + exact: ["foo"] +}) diff --git a/tests/cases/fourslash/completionsObjectLiteralPositionIncludesClassWithPrivateMember4.ts b/tests/cases/fourslash/completionsObjectLiteralPositionIncludesClassWithPrivateMember4.ts new file mode 100644 index 0000000000000..8bb0f9dc73fb1 --- /dev/null +++ b/tests/cases/fourslash/completionsObjectLiteralPositionIncludesClassWithPrivateMember4.ts @@ -0,0 +1,37 @@ +/// + +// @strict: true + +//// type ExampleInit = { +//// foo: number; +//// }; +//// +//// type ExampleLike = Example | ExampleInit; +//// +//// class Example { +//// static isExample(value: any): value is Example { +//// return 'bar' in value; +//// } +//// +//// static from({ exampleLike }: { exampleLike: ExampleLike }): Example { +//// if (Example.isExample(exampleLike)) { +//// return exampleLike; +//// } +//// return new Example(exampleLike); +//// } +//// +//// private bar: number; +//// +//// constructor({ foo }: ExampleInit) { +//// this.bar = foo; +//// } +//// } +//// +//// const example = Example.from({ +//// exampleLike: { /*1*/ }, +//// }); + +verify.completions({ + marker: "1", + exact: ["foo"] +}) From dcff0c72b92bbf6416180378204ae8ab2a3afa53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Fri, 3 Nov 2023 21:34:57 +0100 Subject: [PATCH 2/4] Rename the helper function and reorder some declarations --- src/compiler/checker.ts | 72 ++++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 5e7c72a3b7d0b..862f7aafeffbb 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -30282,7 +30282,42 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return type; } - function getContextualTypeFromParent(node: Expression, contextFlags: ContextFlags | undefined): Type | undefined { + /** + * Whoa! Do you really want to use this function? + * + * Unless you're trying to get the *non-apparent* type for a + * value-literal type or you're authoring relevant portions of this algorithm, + * you probably meant to use 'getApparentTypeOfContextualType'. + * Otherwise this may not be very useful. + * + * In cases where you *are* working on this function, you should understand + * when it is appropriate to use 'getContextualType' and 'getApparentTypeOfContextualType'. + * + * - Use 'getContextualType' when you are simply going to propagate the result to the expression. + * - Use 'getApparentTypeOfContextualType' when you're going to need the members of the type. + * + * @param node the expression whose contextual type will be returned. + * @returns the contextual type of an expression. + */ + function getContextualType(node: Expression, contextFlags: ContextFlags | undefined): Type | undefined { + if (node.flags & NodeFlags.InWithStatement) { + // We cannot answer semantic questions within a with block, do not proceed any further + return undefined; + } + // Cached contextual types are obtained with no ContextFlags, so we can only consult them for + // requests with no ContextFlags. + const index = findContextualNode(node, /*includeCaches*/ !contextFlags); + if (index >= 0) { + return contextualTypes[index]; + } + const contextualType = getContextualTypeWorker(node, contextFlags); + if (!contextualType) { + return undefined; + } + return filterContextualTypeForLiteralExpressionOfObject(node, contextualType); + } + + function getContextualTypeWorker(node: Expression, contextFlags: ContextFlags | undefined): Type | undefined { const { parent } = node; switch (parent.kind) { case SyntaxKind.VariableDeclaration: @@ -30356,41 +30391,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return undefined; } - /** - * Whoa! Do you really want to use this function? - * - * Unless you're trying to get the *non-apparent* type for a - * value-literal type or you're authoring relevant portions of this algorithm, - * you probably meant to use 'getApparentTypeOfContextualType'. - * Otherwise this may not be very useful. - * - * In cases where you *are* working on this function, you should understand - * when it is appropriate to use 'getContextualType' and 'getApparentTypeOfContextualType'. - * - * - Use 'getContextualType' when you are simply going to propagate the result to the expression. - * - Use 'getApparentTypeOfContextualType' when you're going to need the members of the type. - * - * @param node the expression whose contextual type will be returned. - * @returns the contextual type of an expression. - */ - function getContextualType(node: Expression, contextFlags: ContextFlags | undefined): Type | undefined { - if (node.flags & NodeFlags.InWithStatement) { - // We cannot answer semantic questions within a with block, do not proceed any further - return undefined; - } - // Cached contextual types are obtained with no ContextFlags, so we can only consult them for - // requests with no ContextFlags. - const index = findContextualNode(node, /*includeCaches*/ !contextFlags); - if (index >= 0) { - return contextualTypes[index]; - } - const contextualType = getContextualTypeFromParent(node, contextFlags); - if (!contextualType) { - return undefined; - } - return filterContextualTypeForLiteralExpressionOfObject(node, contextualType); - } - function pushCachedContextualType(node: Expression) { pushContextualType(node, getContextualType(node, /*contextFlags*/ undefined), /*isCache*/ true); } From 1601a2251a0c61a0b2c1bcf9eeb5bbdb472f703e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Thu, 4 Jan 2024 23:10:53 +0100 Subject: [PATCH 3/4] add extra test cases --- ...IncludesClassWithPrivateMember1.errors.txt | 41 +++++++++++++++++++ ...ionIncludesClassWithPrivateMember1.symbols | 28 +++++++++++++ ...itionIncludesClassWithPrivateMember1.types | 32 +++++++++++++++ ...IncludesClassWithPrivateMember2.errors.txt | 41 +++++++++++++++++++ ...ionIncludesClassWithPrivateMember2.symbols | 28 +++++++++++++ ...itionIncludesClassWithPrivateMember2.types | 32 +++++++++++++++ ...PositionIncludesClassWithPrivateMember1.ts | 12 ++++++ ...PositionIncludesClassWithPrivateMember2.ts | 12 ++++++ 8 files changed, 226 insertions(+) create mode 100644 tests/baselines/reference/contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.errors.txt create mode 100644 tests/baselines/reference/contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.errors.txt diff --git a/tests/baselines/reference/contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.errors.txt b/tests/baselines/reference/contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.errors.txt new file mode 100644 index 0000000000000..21d2b8fab2565 --- /dev/null +++ b/tests/baselines/reference/contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.errors.txt @@ -0,0 +1,41 @@ +contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.ts(18,8): error TS2345: Argument of type '{ doStuff: (arg: any) => void; }' is not assignable to parameter of type 'Foo'. + Property '#foo' is missing in type '{ doStuff: (arg: any) => void; }' but required in type 'Foo'. +contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.ts(19,13): error TS7006: Parameter 'arg' implicitly has an 'any' type. + + +==== contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.ts (2 errors) ==== + class Foo { + #foo = "foo"; + doStuff(cb: (arg: string) => void) {} + } + + interface FooLike { + doStuff: (cb: (arg: number) => void) => void; + } + + declare function useIt(arg: Foo | FooLike): void; + + useIt({ + doStuff: (arg) => {}, + }); + + declare function useIt2(arg: Foo): void; + + useIt2({ + ~ + doStuff: (arg) => {}, + ~~~~~~~~~~~~~~~~~~~~~~~ + ~~~ +!!! error TS7006: Parameter 'arg' implicitly has an 'any' type. + }); + ~ +!!! error TS2345: Argument of type '{ doStuff: (arg: any) => void; }' is not assignable to parameter of type 'Foo'. +!!! error TS2345: Property '#foo' is missing in type '{ doStuff: (arg: any) => void; }' but required in type 'Foo'. +!!! related TS2728 contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.ts:2:3: '#foo' is declared here. + + declare function useIt3(arg: FooLike): void; + + useIt3({ + doStuff: (arg) => {}, + }); + \ No newline at end of file diff --git a/tests/baselines/reference/contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.symbols b/tests/baselines/reference/contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.symbols index 0a5d5c53645cd..bf2b4e64da5da 100644 --- a/tests/baselines/reference/contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.symbols +++ b/tests/baselines/reference/contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.symbols @@ -37,3 +37,31 @@ useIt({ }); +declare function useIt2(arg: Foo): void; +>useIt2 : Symbol(useIt2, Decl(contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.ts, 13, 3)) +>arg : Symbol(arg, Decl(contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.ts, 15, 24)) +>Foo : Symbol(Foo, Decl(contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.ts, 0, 0)) + +useIt2({ +>useIt2 : Symbol(useIt2, Decl(contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.ts, 13, 3)) + + doStuff: (arg) => {}, +>doStuff : Symbol(doStuff, Decl(contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.ts, 17, 8)) +>arg : Symbol(arg, Decl(contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.ts, 18, 12)) + +}); + +declare function useIt3(arg: FooLike): void; +>useIt3 : Symbol(useIt3, Decl(contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.ts, 19, 3)) +>arg : Symbol(arg, Decl(contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.ts, 21, 24)) +>FooLike : Symbol(FooLike, Decl(contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.ts, 3, 1)) + +useIt3({ +>useIt3 : Symbol(useIt3, Decl(contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.ts, 19, 3)) + + doStuff: (arg) => {}, +>doStuff : Symbol(doStuff, Decl(contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.ts, 23, 8)) +>arg : Symbol(arg, Decl(contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.ts, 24, 12)) + +}); + diff --git a/tests/baselines/reference/contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.types b/tests/baselines/reference/contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.types index 334ea64185af2..c9afc7fbcb03a 100644 --- a/tests/baselines/reference/contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.types +++ b/tests/baselines/reference/contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.types @@ -37,3 +37,35 @@ useIt({ }); +declare function useIt2(arg: Foo): void; +>useIt2 : (arg: Foo) => void +>arg : Foo + +useIt2({ +>useIt2({ doStuff: (arg) => {},}) : void +>useIt2 : (arg: Foo) => void +>{ doStuff: (arg) => {},} : { doStuff: (arg: any) => void; } + + doStuff: (arg) => {}, +>doStuff : (arg: any) => void +>(arg) => {} : (arg: any) => void +>arg : any + +}); + +declare function useIt3(arg: FooLike): void; +>useIt3 : (arg: FooLike) => void +>arg : FooLike + +useIt3({ +>useIt3({ doStuff: (arg) => {},}) : void +>useIt3 : (arg: FooLike) => void +>{ doStuff: (arg) => {},} : { doStuff: (arg: (arg: number) => void) => void; } + + doStuff: (arg) => {}, +>doStuff : (arg: (arg: number) => void) => void +>(arg) => {} : (arg: (arg: number) => void) => void +>arg : (arg: number) => void + +}); + diff --git a/tests/baselines/reference/contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.errors.txt b/tests/baselines/reference/contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.errors.txt new file mode 100644 index 0000000000000..b4a2d1a5a5114 --- /dev/null +++ b/tests/baselines/reference/contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.errors.txt @@ -0,0 +1,41 @@ +contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.ts(18,8): error TS2345: Argument of type '{ doStuff: (arg: any) => void; }' is not assignable to parameter of type 'Foo'. + Property 'foo' is missing in type '{ doStuff: (arg: any) => void; }' but required in type 'Foo'. +contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.ts(19,13): error TS7006: Parameter 'arg' implicitly has an 'any' type. + + +==== contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.ts (2 errors) ==== + class Foo { + private foo = "foo"; + doStuff(cb: (arg: string) => void) {} + } + + interface FooLike { + doStuff: (cb: (arg: number) => void) => void; + } + + declare function useIt(arg: Foo | FooLike): void; + + useIt({ + doStuff: (arg) => {}, + }); + + declare function useIt2(arg: Foo): void; + + useIt2({ + ~ + doStuff: (arg) => {}, + ~~~~~~~~~~~~~~~~~~~~~~~ + ~~~ +!!! error TS7006: Parameter 'arg' implicitly has an 'any' type. + }); + ~ +!!! error TS2345: Argument of type '{ doStuff: (arg: any) => void; }' is not assignable to parameter of type 'Foo'. +!!! error TS2345: Property 'foo' is missing in type '{ doStuff: (arg: any) => void; }' but required in type 'Foo'. +!!! related TS2728 contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.ts:2:11: 'foo' is declared here. + + declare function useIt3(arg: FooLike): void; + + useIt3({ + doStuff: (arg) => {}, + }); + \ No newline at end of file diff --git a/tests/baselines/reference/contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.symbols b/tests/baselines/reference/contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.symbols index a56f2460a4caf..f0e4b7df8f065 100644 --- a/tests/baselines/reference/contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.symbols +++ b/tests/baselines/reference/contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.symbols @@ -37,3 +37,31 @@ useIt({ }); +declare function useIt2(arg: Foo): void; +>useIt2 : Symbol(useIt2, Decl(contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.ts, 13, 3)) +>arg : Symbol(arg, Decl(contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.ts, 15, 24)) +>Foo : Symbol(Foo, Decl(contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.ts, 0, 0)) + +useIt2({ +>useIt2 : Symbol(useIt2, Decl(contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.ts, 13, 3)) + + doStuff: (arg) => {}, +>doStuff : Symbol(doStuff, Decl(contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.ts, 17, 8)) +>arg : Symbol(arg, Decl(contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.ts, 18, 12)) + +}); + +declare function useIt3(arg: FooLike): void; +>useIt3 : Symbol(useIt3, Decl(contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.ts, 19, 3)) +>arg : Symbol(arg, Decl(contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.ts, 21, 24)) +>FooLike : Symbol(FooLike, Decl(contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.ts, 3, 1)) + +useIt3({ +>useIt3 : Symbol(useIt3, Decl(contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.ts, 19, 3)) + + doStuff: (arg) => {}, +>doStuff : Symbol(doStuff, Decl(contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.ts, 23, 8)) +>arg : Symbol(arg, Decl(contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.ts, 24, 12)) + +}); + diff --git a/tests/baselines/reference/contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.types b/tests/baselines/reference/contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.types index 4d141ec121069..eed41173a89e7 100644 --- a/tests/baselines/reference/contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.types +++ b/tests/baselines/reference/contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.types @@ -37,3 +37,35 @@ useIt({ }); +declare function useIt2(arg: Foo): void; +>useIt2 : (arg: Foo) => void +>arg : Foo + +useIt2({ +>useIt2({ doStuff: (arg) => {},}) : void +>useIt2 : (arg: Foo) => void +>{ doStuff: (arg) => {},} : { doStuff: (arg: any) => void; } + + doStuff: (arg) => {}, +>doStuff : (arg: any) => void +>(arg) => {} : (arg: any) => void +>arg : any + +}); + +declare function useIt3(arg: FooLike): void; +>useIt3 : (arg: FooLike) => void +>arg : FooLike + +useIt3({ +>useIt3({ doStuff: (arg) => {},}) : void +>useIt3 : (arg: FooLike) => void +>{ doStuff: (arg) => {},} : { doStuff: (arg: (arg: number) => void) => void; } + + doStuff: (arg) => {}, +>doStuff : (arg: (arg: number) => void) => void +>(arg) => {} : (arg: (arg: number) => void) => void +>arg : (arg: number) => void + +}); + diff --git a/tests/cases/compiler/contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.ts b/tests/cases/compiler/contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.ts index 5d5f6f1bbba1d..19ccc3db67174 100644 --- a/tests/cases/compiler/contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.ts +++ b/tests/cases/compiler/contextuallyTypedParametersPositionIncludesClassWithPrivateMember1.ts @@ -16,3 +16,15 @@ declare function useIt(arg: Foo | FooLike): void; useIt({ doStuff: (arg) => {}, }); + +declare function useIt2(arg: Foo): void; + +useIt2({ + doStuff: (arg) => {}, +}); + +declare function useIt3(arg: FooLike): void; + +useIt3({ + doStuff: (arg) => {}, +}); diff --git a/tests/cases/compiler/contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.ts b/tests/cases/compiler/contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.ts index ab7432439fde3..d985d05f3900c 100644 --- a/tests/cases/compiler/contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.ts +++ b/tests/cases/compiler/contextuallyTypedParametersPositionIncludesClassWithPrivateMember2.ts @@ -15,3 +15,15 @@ declare function useIt(arg: Foo | FooLike): void; useIt({ doStuff: (arg) => {}, }); + +declare function useIt2(arg: Foo): void; + +useIt2({ + doStuff: (arg) => {}, +}); + +declare function useIt3(arg: FooLike): void; + +useIt3({ + doStuff: (arg) => {}, +}); From 7c9a2feb47988f21b563b2ba5ca4dceaae883cd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Fri, 5 Jan 2024 00:28:27 +0100 Subject: [PATCH 4/4] just reassign a parameter --- src/compiler/checker.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index bcbd42f053ff3..3c2e75a2eb53b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -38830,9 +38830,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function checkExpressionWithContextualType(node: Expression, contextualType: Type, inferenceContext: InferenceContext | undefined, checkMode: CheckMode): Type { - const filteredContextualType = filterContextualTypeForLiteralExpressionOfObject(node, contextualType); + contextualType = filterContextualTypeForLiteralExpressionOfObject(node, contextualType); const contextNode = getContextNode(node); - pushContextualType(contextNode, filteredContextualType, /*isCache*/ false); + pushContextualType(contextNode, contextualType, /*isCache*/ false); pushInferenceContext(contextNode, inferenceContext); const type = checkExpression(node, checkMode | CheckMode.Contextual | (inferenceContext ? CheckMode.Inferential : 0)); // In CheckMode.Inferential we collect intra-expression inference sites to process before fixing any type @@ -38843,7 +38843,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // We strip literal freshness when an appropriate contextual type is present such that contextually typed // literals always preserve their literal types (otherwise they might widen during type inference). An alternative // here would be to not mark contextually typed literals as fresh in the first place. - const result = maybeTypeOfKind(type, TypeFlags.Literal) && isLiteralOfContextualType(type, instantiateContextualType(filteredContextualType, node, /*contextFlags*/ undefined)) ? + const result = maybeTypeOfKind(type, TypeFlags.Literal) && isLiteralOfContextualType(type, instantiateContextualType(contextualType, node, /*contextFlags*/ undefined)) ? getRegularTypeOfLiteralType(type) : type; popInferenceContext(); popContextualType();