Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow narrowing {} originating in unknown using instanceof #51014

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25891,6 +25891,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (!nonConstructorTypeInUnion) return type;
}

if (assumeTrue && declaredType === unknownType && isEmptyAnonymousObjectType(type)) {
return targetType;
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem with conditioning this on the declared type is that it doesn't work when the empty anonymous object type originates in some other type. For example:

function f<T>(x: T & {}) {
    return x instanceof Object && 'a' in x;  // Error, but shouldn't be
}

return getNarrowedType(type, targetType, assumeTrue, /*checkDerived*/ true);
}

Expand Down
13 changes: 13 additions & 0 deletions tests/baselines/reference/inKeywordAndUnknown.errors.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,17 @@ tests/cases/compiler/inKeywordAndUnknown.ts(12,18): error TS2638: Type '{}' may
}
y; // {}
}


// repro #51007
function isHTMLTable(table: unknown): boolean {
return !!table && table instanceof Object && 'html' in table;
}

function tryGetHtmlPropFromTable(table: unknown) {
if (!!table && table instanceof Object && 'html' in table && table.html instanceof Object) {
return table.html;
}
return undefined;
}

23 changes: 23 additions & 0 deletions tests/baselines/reference/inKeywordAndUnknown.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,19 @@ function f(x: {}, y: unknown) {
}
y; // {}
}


// repro #51007
function isHTMLTable(table: unknown): boolean {
return !!table && table instanceof Object && 'html' in table;
}

function tryGetHtmlPropFromTable(table: unknown) {
if (!!table && table instanceof Object && 'html' in table && table.html instanceof Object) {
return table.html;
}
return undefined;
}


//// [inKeywordAndUnknown.js]
Expand All @@ -34,3 +47,13 @@ function f(x, y) {
}
y; // {}
}
// repro #51007
function isHTMLTable(table) {
return !!table && table instanceof Object && 'html' in table;
}
function tryGetHtmlPropFromTable(table) {
if (!!table && table instanceof Object && 'html' in table && table.html instanceof Object) {
return table.html;
}
return undefined;
}
36 changes: 36 additions & 0 deletions tests/baselines/reference/inKeywordAndUnknown.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,39 @@ function f(x: {}, y: unknown) {
>y : Symbol(y, Decl(inKeywordAndUnknown.ts, 2, 17))
}


// repro #51007
function isHTMLTable(table: unknown): boolean {
>isHTMLTable : Symbol(isHTMLTable, Decl(inKeywordAndUnknown.ts, 15, 1))
>table : Symbol(table, Decl(inKeywordAndUnknown.ts, 19, 21))

return !!table && table instanceof Object && 'html' in table;
>table : Symbol(table, Decl(inKeywordAndUnknown.ts, 19, 21))
>table : Symbol(table, Decl(inKeywordAndUnknown.ts, 19, 21))
>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>table : Symbol(table, Decl(inKeywordAndUnknown.ts, 19, 21))
}

function tryGetHtmlPropFromTable(table: unknown) {
>tryGetHtmlPropFromTable : Symbol(tryGetHtmlPropFromTable, Decl(inKeywordAndUnknown.ts, 21, 1))
>table : Symbol(table, Decl(inKeywordAndUnknown.ts, 23, 33))

if (!!table && table instanceof Object && 'html' in table && table.html instanceof Object) {
>table : Symbol(table, Decl(inKeywordAndUnknown.ts, 23, 33))
>table : Symbol(table, Decl(inKeywordAndUnknown.ts, 23, 33))
>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>table : Symbol(table, Decl(inKeywordAndUnknown.ts, 23, 33))
>table.html : Symbol(html)
>table : Symbol(table, Decl(inKeywordAndUnknown.ts, 23, 33))
>html : Symbol(html)
>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))

return table.html;
>table.html : Symbol(html)
>table : Symbol(table, Decl(inKeywordAndUnknown.ts, 23, 33))
>html : Symbol(html)
}
return undefined;
>undefined : Symbol(undefined)
}

52 changes: 52 additions & 0 deletions tests/baselines/reference/inKeywordAndUnknown.types
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,55 @@ function f(x: {}, y: unknown) {
>y : Record<"a", unknown>
}


// repro #51007
function isHTMLTable(table: unknown): boolean {
>isHTMLTable : (table: unknown) => boolean
>table : unknown

return !!table && table instanceof Object && 'html' in table;
>!!table && table instanceof Object && 'html' in table : boolean
>!!table && table instanceof Object : boolean
>!!table : boolean
>!table : boolean
>table : unknown
>table instanceof Object : boolean
>table : {}
>Object : ObjectConstructor
>'html' in table : boolean
>'html' : "html"
>table : Object
}

function tryGetHtmlPropFromTable(table: unknown) {
>tryGetHtmlPropFromTable : (table: unknown) => Object | undefined
>table : unknown

if (!!table && table instanceof Object && 'html' in table && table.html instanceof Object) {
>!!table && table instanceof Object && 'html' in table && table.html instanceof Object : boolean
>!!table && table instanceof Object && 'html' in table : boolean
>!!table && table instanceof Object : boolean
>!!table : boolean
>!table : boolean
>table : unknown
>table instanceof Object : boolean
>table : {}
>Object : ObjectConstructor
>'html' in table : boolean
>'html' : "html"
>table : Object
>table.html instanceof Object : boolean
>table.html : unknown
>table : Object & Record<"html", unknown>
>html : unknown
>Object : ObjectConstructor

return table.html;
>table.html : Object
>table : Object & Record<"html", unknown>
>html : Object
}
return undefined;
>undefined : undefined
}

13 changes: 13 additions & 0 deletions tests/cases/compiler/inKeywordAndUnknown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,16 @@ function f(x: {}, y: unknown) {
}
y; // {}
}


// repro #51007
function isHTMLTable(table: unknown): boolean {
return !!table && table instanceof Object && 'html' in table;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the test here should actually test for an access of table.html in some capacity - e.g.

function tryGetHtmlPropFromTable(table: unknown) {
    if (!!table && table instanceof Object && 'html' in table && table.html instanceof Object) {
        return table.html;
    }
    return undefined;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hm, what would it change here? the reported error was that in couldn't be used at all in this situation - we could add more "assertions" here than what was originally reported but I'm not sure what this particular snippet would bring to the table here. What kind of nuance am I missing? 😅

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original test is fine; having another test that actually tries to narrow and use the newly-tested property corresponds to something closer to real-world usage.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I've pushed out the proposed test case - please take a look if this is what you had in mind here.

}

function tryGetHtmlPropFromTable(table: unknown) {
if (!!table && table instanceof Object && 'html' in table && table.html instanceof Object) {
return table.html;
}
return undefined;
}