Skip to content

Fix check for generic types in control flow analysis #45148

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

Merged
merged 4 commits into from
Aug 6, 2021
Merged
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
55 changes: 23 additions & 32 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14857,40 +14857,35 @@ namespace ts {
return !!(type.flags & TypeFlags.TemplateLiteral) && every((type as TemplateLiteralType).types, isPatternLiteralPlaceholderType);
}

function isGenericType(type: Type): boolean {
return !!getGenericObjectFlags(type);
}

function isGenericObjectType(type: Type): boolean {
if (type.flags & TypeFlags.UnionOrIntersection) {
if (!((type as UnionOrIntersectionType).objectFlags & ObjectFlags.IsGenericObjectTypeComputed)) {
(type as UnionOrIntersectionType).objectFlags |= ObjectFlags.IsGenericObjectTypeComputed |
(some((type as UnionOrIntersectionType).types, isGenericObjectType) ? ObjectFlags.IsGenericObjectType : 0);
}
return !!((type as UnionOrIntersectionType).objectFlags & ObjectFlags.IsGenericObjectType);
}
if (type.flags & TypeFlags.Substitution) {
if (!((type as SubstitutionType).objectFlags & ObjectFlags.IsGenericObjectTypeComputed)) {
(type as SubstitutionType).objectFlags |= ObjectFlags.IsGenericObjectTypeComputed |
(isGenericObjectType((type as SubstitutionType).substitute) || isGenericObjectType((type as SubstitutionType).baseType) ? ObjectFlags.IsGenericObjectType : 0);
}
return !!((type as SubstitutionType).objectFlags & ObjectFlags.IsGenericObjectType);
}
return !!(type.flags & TypeFlags.InstantiableNonPrimitive) || isGenericMappedType(type) || isGenericTupleType(type);
return !!(getGenericObjectFlags(type) & ObjectFlags.IsGenericObjectType);
}

function isGenericIndexType(type: Type): boolean {
return !!(getGenericObjectFlags(type) & ObjectFlags.IsGenericIndexType);
}

function getGenericObjectFlags(type: Type): ObjectFlags {
if (type.flags & TypeFlags.UnionOrIntersection) {
if (!((type as UnionOrIntersectionType).objectFlags & ObjectFlags.IsGenericIndexTypeComputed)) {
(type as UnionOrIntersectionType).objectFlags |= ObjectFlags.IsGenericIndexTypeComputed |
(some((type as UnionOrIntersectionType).types, isGenericIndexType) ? ObjectFlags.IsGenericIndexType : 0);
if (!((type as UnionOrIntersectionType).objectFlags & ObjectFlags.IsGenericTypeComputed)) {
(type as UnionOrIntersectionType).objectFlags |= ObjectFlags.IsGenericTypeComputed |
reduceLeft((type as UnionOrIntersectionType).types, (flags, t) => flags | getGenericObjectFlags(t), 0);
}
return !!((type as UnionOrIntersectionType).objectFlags & ObjectFlags.IsGenericIndexType);
return (type as UnionOrIntersectionType).objectFlags & ObjectFlags.IsGenericType;
}
if (type.flags & TypeFlags.Substitution) {
if (!((type as SubstitutionType).objectFlags & ObjectFlags.IsGenericIndexTypeComputed)) {
(type as SubstitutionType).objectFlags |= ObjectFlags.IsGenericIndexTypeComputed |
(isGenericIndexType((type as SubstitutionType).substitute) || isGenericIndexType((type as SubstitutionType).baseType) ? ObjectFlags.IsGenericIndexType : 0);
if (!((type as SubstitutionType).objectFlags & ObjectFlags.IsGenericTypeComputed)) {
(type as SubstitutionType).objectFlags |= ObjectFlags.IsGenericTypeComputed |
getGenericObjectFlags((type as SubstitutionType).substitute) | getGenericObjectFlags((type as SubstitutionType).baseType);
}
return !!((type as SubstitutionType).objectFlags & ObjectFlags.IsGenericIndexType);
return (type as SubstitutionType).objectFlags & ObjectFlags.IsGenericType;
}
return !!(type.flags & (TypeFlags.InstantiableNonPrimitive | TypeFlags.Index | TypeFlags.TemplateLiteral | TypeFlags.StringMapping)) && !isPatternLiteralType(type);
return (type.flags & TypeFlags.InstantiableNonPrimitive || isGenericMappedType(type) || isGenericTupleType(type) ? ObjectFlags.IsGenericObjectType : 0) |
(type.flags & (TypeFlags.InstantiableNonPrimitive | TypeFlags.Index | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) && !isPatternLiteralType(type) ? ObjectFlags.IsGenericIndexType : 0);
}

function isThisTypeParameter(type: Type): boolean {
Expand Down Expand Up @@ -15167,7 +15162,7 @@ namespace ts {
while (true) {
const isUnwrapped = isTypicalNondistributiveConditional(root);
const checkType = instantiateType(unwrapNondistributiveConditionalTuple(root, getActualTypeVariable(root.checkType)), mapper);
const checkTypeInstantiable = isGenericObjectType(checkType) || isGenericIndexType(checkType);
const checkTypeInstantiable = isGenericType(checkType);
const extendsType = instantiateType(unwrapNondistributiveConditionalTuple(root, root.extendsType), mapper);
if (checkType === wildcardType || extendsType === wildcardType) {
return wildcardType;
Expand All @@ -15189,7 +15184,7 @@ namespace ts {
// Instantiate the extends type including inferences for 'infer T' type parameters
const inferredExtendsType = combinedMapper ? instantiateType(unwrapNondistributiveConditionalTuple(root, root.extendsType), combinedMapper) : extendsType;
// We attempt to resolve the conditional type only when the check and extends types are non-generic
if (!checkTypeInstantiable && !isGenericObjectType(inferredExtendsType) && !isGenericIndexType(inferredExtendsType)) {
if (!checkTypeInstantiable && !isGenericType(inferredExtendsType)) {
// Return falseType for a definitely false extends check. We check an instantiations of the two
// types with type parameters mapped to the wildcard type, the most permissive instantiations
// possible (the wildcard type is assignable to and from all types). If those are not related,
Expand Down Expand Up @@ -24242,17 +24237,13 @@ namespace ts {
return !!(type.flags & TypeFlags.Instantiable && !maybeTypeOfKind(getBaseConstraintOrType(type), TypeFlags.Nullable));
}

function containsGenericType(type: Type): boolean {
return !!(type.flags & TypeFlags.Instantiable || type.flags & TypeFlags.UnionOrIntersection && some((type as UnionOrIntersectionType).types, containsGenericType));
}

function hasNonBindingPatternContextualTypeWithNoGenericTypes(node: Node) {
// Computing the contextual type for a child of a JSX element involves resolving the type of the
// element's tag name, so we exclude that here to avoid circularities.
const contextualType = (isIdentifier(node) || isPropertyAccessExpression(node) || isElementAccessExpression(node)) &&
!((isJsxOpeningElement(node.parent) || isJsxSelfClosingElement(node.parent)) && node.parent.tagName === node) &&
getContextualType(node, ContextFlags.SkipBindingPatterns);
return contextualType && !someType(contextualType, containsGenericType);
return contextualType && !isGenericType(contextualType);
}

function getNarrowableTypeForReference(type: Type, reference: Node, checkMode?: CheckMode) {
Expand Down Expand Up @@ -41582,7 +41573,7 @@ namespace ts {
return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_parameter_must_have_a_type_annotation);
}
const type = getTypeFromTypeNode(parameter.type);
if (someType(type, t => !!(t.flags & TypeFlags.StringOrNumberLiteralOrUnique)) || isGenericIndexType(type) || isGenericObjectType(type)) {
if (someType(type, t => !!(t.flags & TypeFlags.StringOrNumberLiteralOrUnique)) || isGenericType(type)) {
return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_parameter_type_cannot_be_a_literal_type_or_generic_type_Consider_using_a_mapped_object_type_instead);
}
if (!everyType(type, isValidIndexKeyType)) {
Expand Down
12 changes: 6 additions & 6 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5263,23 +5263,23 @@ namespace ts {

// Flags that require TypeFlags.UnionOrIntersection or TypeFlags.Substitution
/* @internal */
IsGenericObjectTypeComputed = 1 << 22, // IsGenericObjectType flag has been computed
IsGenericTypeComputed = 1 << 22, // IsGenericObjectType flag has been computed
/* @internal */
IsGenericObjectType = 1 << 23, // Union or intersection contains generic object type
/* @internal */
IsGenericIndexTypeComputed = 1 << 24, // IsGenericIndexType flag has been computed
IsGenericIndexType = 1 << 24, // Union or intersection contains generic index type
/* @internal */
IsGenericIndexType = 1 << 25, // Union or intersection contains generic index type
IsGenericType = IsGenericObjectType | IsGenericIndexType,

// Flags that require TypeFlags.Union
/* @internal */
ContainsIntersections = 1 << 26, // Union contains intersections
ContainsIntersections = 1 << 25, // Union contains intersections

// Flags that require TypeFlags.Intersection
/* @internal */
IsNeverIntersectionComputed = 1 << 26, // IsNeverLike flag has been computed
IsNeverIntersectionComputed = 1 << 25, // IsNeverLike flag has been computed
/* @internal */
IsNeverIntersection = 1 << 27, // Intersection reduces to never
IsNeverIntersection = 1 << 26, // Intersection reduces to never
}

/* @internal */
Expand Down
16 changes: 16 additions & 0 deletions tests/baselines/reference/controlFlowGenericTypes.errors.txt
Original file line number Diff line number Diff line change
Expand Up @@ -204,4 +204,20 @@ tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts(168,9): error TS2
iSpec[null! as keyof PublicSpec];
}
}

// Repros from #45145

function f10<T extends { a: string } | undefined>(x: T, y: Partial<T>) {
y = x;
}

type SqlInsertSet<T> = T extends undefined ? object : { [P in keyof T]: unknown };

class SqlTable<T> {
protected validateRow(_row: Partial<SqlInsertSet<T>>): void {
}
public insertRow(row: SqlInsertSet<T>) {
this.validateRow(row);
}
}

30 changes: 30 additions & 0 deletions tests/baselines/reference/controlFlowGenericTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,22 @@ class TableBaseEnum<
iSpec[null! as keyof PublicSpec];
}
}

// Repros from #45145

function f10<T extends { a: string } | undefined>(x: T, y: Partial<T>) {
y = x;
}

type SqlInsertSet<T> = T extends undefined ? object : { [P in keyof T]: unknown };

class SqlTable<T> {
protected validateRow(_row: Partial<SqlInsertSet<T>>): void {
}
public insertRow(row: SqlInsertSet<T>) {
this.validateRow(row);
}
}


//// [controlFlowGenericTypes.js]
Expand Down Expand Up @@ -313,3 +329,17 @@ var TableBaseEnum = /** @class */ (function () {
};
return TableBaseEnum;
}());
// Repros from #45145
function f10(x, y) {
y = x;
}
var SqlTable = /** @class */ (function () {
function SqlTable() {
}
SqlTable.prototype.validateRow = function (_row) {
};
SqlTable.prototype.insertRow = function (row) {
this.validateRow(row);
};
return SqlTable;
}());
49 changes: 49 additions & 0 deletions tests/baselines/reference/controlFlowGenericTypes.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -525,3 +525,52 @@ class TableBaseEnum<
}
}

// Repros from #45145

function f10<T extends { a: string } | undefined>(x: T, y: Partial<T>) {
>f10 : Symbol(f10, Decl(controlFlowGenericTypes.ts, 174, 1))
>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 178, 13))
>a : Symbol(a, Decl(controlFlowGenericTypes.ts, 178, 24))
>x : Symbol(x, Decl(controlFlowGenericTypes.ts, 178, 50))
>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 178, 13))
>y : Symbol(y, Decl(controlFlowGenericTypes.ts, 178, 55))
>Partial : Symbol(Partial, Decl(lib.es5.d.ts, --, --))
>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 178, 13))

y = x;
>y : Symbol(y, Decl(controlFlowGenericTypes.ts, 178, 55))
>x : Symbol(x, Decl(controlFlowGenericTypes.ts, 178, 50))
}

type SqlInsertSet<T> = T extends undefined ? object : { [P in keyof T]: unknown };
>SqlInsertSet : Symbol(SqlInsertSet, Decl(controlFlowGenericTypes.ts, 180, 1))
>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 182, 18))
>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 182, 18))
>P : Symbol(P, Decl(controlFlowGenericTypes.ts, 182, 57))
>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 182, 18))

class SqlTable<T> {
>SqlTable : Symbol(SqlTable, Decl(controlFlowGenericTypes.ts, 182, 82))
>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 184, 15))

protected validateRow(_row: Partial<SqlInsertSet<T>>): void {
>validateRow : Symbol(SqlTable.validateRow, Decl(controlFlowGenericTypes.ts, 184, 19))
>_row : Symbol(_row, Decl(controlFlowGenericTypes.ts, 185, 26))
>Partial : Symbol(Partial, Decl(lib.es5.d.ts, --, --))
>SqlInsertSet : Symbol(SqlInsertSet, Decl(controlFlowGenericTypes.ts, 180, 1))
>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 184, 15))
}
public insertRow(row: SqlInsertSet<T>) {
>insertRow : Symbol(SqlTable.insertRow, Decl(controlFlowGenericTypes.ts, 186, 5))
>row : Symbol(row, Decl(controlFlowGenericTypes.ts, 187, 21))
>SqlInsertSet : Symbol(SqlInsertSet, Decl(controlFlowGenericTypes.ts, 180, 1))
>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 184, 15))

this.validateRow(row);
>this.validateRow : Symbol(SqlTable.validateRow, Decl(controlFlowGenericTypes.ts, 184, 19))
>this : Symbol(SqlTable, Decl(controlFlowGenericTypes.ts, 182, 82))
>validateRow : Symbol(SqlTable.validateRow, Decl(controlFlowGenericTypes.ts, 184, 19))
>row : Symbol(row, Decl(controlFlowGenericTypes.ts, 187, 21))
}
}

37 changes: 37 additions & 0 deletions tests/baselines/reference/controlFlowGenericTypes.types
Original file line number Diff line number Diff line change
Expand Up @@ -505,3 +505,40 @@ class TableBaseEnum<
}
}

// Repros from #45145

function f10<T extends { a: string } | undefined>(x: T, y: Partial<T>) {
>f10 : <T extends { a: string; } | undefined>(x: T, y: Partial<T>) => void
>a : string
>x : T
>y : Partial<T>

y = x;
>y = x : T
>y : Partial<T>
>x : T
}

type SqlInsertSet<T> = T extends undefined ? object : { [P in keyof T]: unknown };
>SqlInsertSet : SqlInsertSet<T>

class SqlTable<T> {
>SqlTable : SqlTable<T>

protected validateRow(_row: Partial<SqlInsertSet<T>>): void {
>validateRow : (_row: Partial<SqlInsertSet<T>>) => void
>_row : Partial<SqlInsertSet<T>>
}
public insertRow(row: SqlInsertSet<T>) {
>insertRow : (row: SqlInsertSet<T>) => void
>row : SqlInsertSet<T>

this.validateRow(row);
>this.validateRow(row) : void
>this.validateRow : (_row: Partial<SqlInsertSet<T>>) => void
>this : this
>validateRow : (_row: Partial<SqlInsertSet<T>>) => void
>row : SqlInsertSet<T>
}
}

16 changes: 16 additions & 0 deletions tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,3 +175,19 @@ class TableBaseEnum<
iSpec[null! as keyof PublicSpec];
}
}

// Repros from #45145

function f10<T extends { a: string } | undefined>(x: T, y: Partial<T>) {
y = x;
}

type SqlInsertSet<T> = T extends undefined ? object : { [P in keyof T]: unknown };

class SqlTable<T> {
protected validateRow(_row: Partial<SqlInsertSet<T>>): void {
}
public insertRow(row: SqlInsertSet<T>) {
this.validateRow(row);
}
}