diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ce1ee54edcde4..05f141ea3794c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7955,10 +7955,11 @@ namespace ts { } } - function getSubstitutionType(typeVariable: TypeVariable, substitute: Type) { + function getSubstitutionType(typeVariable: Type, substitute: Type, negaConstraints: Type[] | undefined) { const result = createType(TypeFlags.Substitution); result.typeVariable = typeVariable; result.substitute = substitute; + result.negatedTypes = negaConstraints; return result; } @@ -7966,25 +7967,31 @@ namespace ts { return node.kind === SyntaxKind.TupleType && (node).elementTypes.length === 1; } - function getImpliedConstraint(typeVariable: TypeVariable, checkNode: TypeNode, extendsNode: TypeNode): Type | undefined { + function getImpliedConstraint(typeVariable: Type, checkNode: TypeNode, extendsNode: TypeNode): Type | undefined { return isUnaryTupleTypeNode(checkNode) && isUnaryTupleTypeNode(extendsNode) ? getImpliedConstraint(typeVariable, (checkNode).elementTypes[0], (extendsNode).elementTypes[0]) : getActualTypeVariable(getTypeFromTypeNode(checkNode)) === typeVariable ? getTypeFromTypeNode(extendsNode) : undefined; } - function getConstrainedTypeVariable(typeVariable: TypeVariable, node: Node) { + function getConstrainedTypeVariable(typeVariable: Type, node: Node) { let constraints: Type[] | undefined; + let negatedConstraints: Type[] | undefined; while (node && !isStatement(node) && node.kind !== SyntaxKind.JSDocComment) { const parent = node.parent; - if (parent.kind === SyntaxKind.ConditionalType && node === (parent).trueType) { + if (parent.kind === SyntaxKind.ConditionalType && (node === (parent).trueType || node === (parent).falseType)) { const constraint = getImpliedConstraint(typeVariable, (parent).checkType, (parent).extendsType); if (constraint) { - constraints = append(constraints, constraint); + if (node === (parent).trueType) { + constraints = append(constraints, constraint); + } + else { + negatedConstraints = append(negatedConstraints, constraint); + } } } node = parent; } - return constraints ? getSubstitutionType(typeVariable, getIntersectionType(append(constraints, typeVariable))) : typeVariable; + return (constraints || negatedConstraints) ? getSubstitutionType(typeVariable, getIntersectionType(append(constraints, typeVariable)), negatedConstraints) : typeVariable; } function isJSDocTypeReference(node: Node): node is TypeReferenceNode { @@ -8069,7 +8076,7 @@ namespace ts { // Cache both the resolved symbol and the resolved type. The resolved symbol is needed in when we check the // type reference in checkTypeReferenceNode. links.resolvedSymbol = symbol; - links.resolvedType = type; + links.resolvedType = getConstrainedTypeVariable(type, node); } return links.resolvedType; } @@ -8085,7 +8092,7 @@ namespace ts { // The expression is processed as an identifier expression (section 4.3) // or property access expression(section 4.10), // the widened type(section 3.9) of which becomes the result. - links.resolvedType = getWidenedType(checkExpression(node.exprName)); + links.resolvedType = getConstrainedTypeVariable(getWidenedType(checkExpression(node.exprName)), node); } return links.resolvedType; } @@ -8238,7 +8245,7 @@ namespace ts { function getTypeFromArrayTypeNode(node: ArrayTypeNode): Type { const links = getNodeLinks(node); if (!links.resolvedType) { - links.resolvedType = createArrayType(getTypeFromTypeNode(node.elementType)); + links.resolvedType = getConstrainedTypeVariable(createArrayType(getTypeFromTypeNode(node.elementType)), node); } return links.resolvedType; } @@ -8293,7 +8300,7 @@ namespace ts { function getTypeFromTupleTypeNode(node: TupleTypeNode): Type { const links = getNodeLinks(node); if (!links.resolvedType) { - links.resolvedType = createTupleType(map(node.elementTypes, getTypeFromTypeNode)); + links.resolvedType = getConstrainedTypeVariable(createTupleType(map(node.elementTypes, getTypeFromTypeNode)), node); } return links.resolvedType; } @@ -8543,8 +8550,8 @@ namespace ts { const links = getNodeLinks(node); if (!links.resolvedType) { const aliasSymbol = getAliasSymbolForTypeNode(node); - links.resolvedType = getUnionType(map(node.types, getTypeFromTypeNode), UnionReduction.Literal, - aliasSymbol, getTypeArgumentsForAliasSymbol(aliasSymbol)); + links.resolvedType = getConstrainedTypeVariable(getUnionType(map(node.types, getTypeFromTypeNode), UnionReduction.Literal, + aliasSymbol, getTypeArgumentsForAliasSymbol(aliasSymbol)), node); } return links.resolvedType; } @@ -8686,8 +8693,8 @@ namespace ts { const links = getNodeLinks(node); if (!links.resolvedType) { const aliasSymbol = getAliasSymbolForTypeNode(node); - links.resolvedType = getIntersectionType(map(node.types, getTypeFromTypeNode), - aliasSymbol, getTypeArgumentsForAliasSymbol(aliasSymbol)); + links.resolvedType = getConstrainedTypeVariable(getIntersectionType(map(node.types, getTypeFromTypeNode), + aliasSymbol, getTypeArgumentsForAliasSymbol(aliasSymbol)), node); } return links.resolvedType; } @@ -8761,7 +8768,7 @@ namespace ts { if (!links.resolvedType) { switch (node.operator) { case SyntaxKind.KeyOfKeyword: - links.resolvedType = getIndexType(getTypeFromTypeNode(node.type)); + links.resolvedType = getConstrainedTypeVariable(getIndexType(getTypeFromTypeNode(node.type)), node); break; case SyntaxKind.UniqueKeyword: links.resolvedType = node.type.kind === SyntaxKind.SymbolKeyword @@ -8987,7 +8994,7 @@ namespace ts { links.resolvedType = resolved.flags & TypeFlags.IndexedAccess && (resolved).objectType === objectType && (resolved).indexType === indexType ? - getConstrainedTypeVariable(resolved, node) : resolved; + getConstrainedTypeVariable(resolved, node) : resolved; } return links.resolvedType; } @@ -8999,7 +9006,7 @@ namespace ts { type.declaration = node; type.aliasSymbol = getAliasSymbolForTypeNode(node); type.aliasTypeArguments = getTypeArgumentsForAliasSymbol(type.aliasSymbol); - links.resolvedType = type; + links.resolvedType = getConstrainedTypeVariable(type, node); // Eagerly resolve the constraint type which forces an error if the constraint type circularly // references itself through one or more type aliases. getConstraintTypeFromMappedType(type); @@ -9126,7 +9133,7 @@ namespace ts { aliasSymbol, aliasTypeArguments }; - links.resolvedType = getConditionalType(root, /*mapper*/ undefined); + links.resolvedType = getConstrainedTypeVariable(getConditionalType(root, /*mapper*/ undefined), node); if (outerTypeParameters) { root.instantiations = createMap(); root.instantiations.set(getTypeListId(outerTypeParameters), links.resolvedType); @@ -9210,10 +9217,10 @@ namespace ts { const resolvedSymbol = resolveSymbol(symbol); links.resolvedSymbol = resolvedSymbol; if (meaning === SymbolFlags.Value) { - return links.resolvedType = getTypeOfSymbol(symbol); // intentionally doesn't use resolved symbol so type is cached as expected on the alias + return links.resolvedType = getConstrainedTypeVariable(getTypeOfSymbol(symbol), node); // intentionally doesn't use resolved symbol so type is cached as expected on the alias } else { - return links.resolvedType = getTypeReferenceType(node, resolvedSymbol); // getTypeReferenceType doesn't handle aliases - it must get the resolved symbol + return links.resolvedType = getConstrainedTypeVariable(getTypeReferenceType(node, resolvedSymbol), node); // getTypeReferenceType doesn't handle aliases - it must get the resolved symbol } } @@ -9232,7 +9239,7 @@ namespace ts { if (isJSDocTypeLiteral(node) && node.isArrayType) { type = createArrayType(type); } - links.resolvedType = type; + links.resolvedType = getConstrainedTypeVariable(type, node); } } return links.resolvedType; @@ -9440,7 +9447,7 @@ namespace ts { function getTypeFromThisTypeNode(node: ThisExpression | ThisTypeNode): Type { const links = getNodeLinks(node); if (!links.resolvedType) { - links.resolvedType = getThisType(node); + links.resolvedType = getConstrainedTypeVariable(getThisType(node), node); } return links.resolvedType; } @@ -10396,6 +10403,52 @@ namespace ts { return getObjectFlags(source) & ObjectFlags.JsxAttributes && !(isUnhyphenatedJsxName(sourceProp.escapedName) || targetMemberType); } + function collectTypeParameters(type: Type) { + const params: TypeParameter[] = []; + instantiateType(type, t => { + if (t.flags & TypeFlags.TypeParameter) { + params.push(t); + } + return t; + }); + return params; + } + + /** + * Runs a conditional type "backward" and returns weather the relationship check is true + * @param source The left-hand side of the relation + * @param target The conditional type on the right-hand side of the relation + * @param inferenceTarget The type that should be used as the inference target + * @param relation The relation to use for the comparison + */ + function conditionalTypeReverseInferenceSucceeds(source: Type, target: ConditionalType, inferenceTarget: Type, relation: Map) { + inferenceTarget = inferenceTarget.flags & TypeFlags.Substitution ? (inferenceTarget as SubstitutionType).typeVariable : inferenceTarget; + let checkType = target.checkType; + let mapper = identityMapper; + // target.root.outerTypeParameters indicates that this conditional type has some type variables which may be unbound + if (target.root.outerTypeParameters) { + // First, infer from source to the inference target + const params = collectTypeParameters(inferenceTarget); + // We `SkipConstraintCheck` in the below so that when we draw an inference from `"foo"` to `keyof T` for `T`, we don't replace the + // result with `T`'s constraint. + const context = createInferenceContext(params, /*signature*/ undefined, InferenceFlags.SkipConstraintCheck); + inferTypes(context.inferences, source, inferenceTarget, InferencePriority.NoConstraints | InferencePriority.AlwaysStrict); + const results = getInferredTypes(context); + // And instantiate the checkType with the results + checkType = instantiateType(target.checkType, mapper = createTypeMapper(params, results)); + } + let extendsType = target.extendsType; + if (target.root.inferTypeParameters) { + // Then, infer from the instantiated checkType to the extendsType + const context = createInferenceContext(target.root.inferTypeParameters, /*signature*/ undefined, InferenceFlags.None); + inferTypes(context.inferences, checkType, extendsType, InferencePriority.NoConstraints | InferencePriority.AlwaysStrict); + // And instantiate it with the inferences + extendsType = instantiateType(extendsType, combineTypeMappers(mapper, context)); + } + // Finally, check if the statement is true + return checkTypeRelatedTo(checkType, extendsType, relation, /*errorNode*/ undefined); + } + /** * Checks if 'source' is related to 'target' (e.g.: is a assignable to). * @param source The left-hand-side of the relation. @@ -10529,7 +10582,15 @@ namespace ts { source = relation === definitelyAssignableRelation ? (source).typeVariable : (source).substitute; } if (target.flags & TypeFlags.Substitution) { - target = (target).typeVariable; + const negaTypes = (target as SubstitutionType).negatedTypes; + if (negaTypes) { + for (const type of negaTypes) { + if (isRelatedTo(source, type)) { + return Ternary.False; + } + } + } + target = (target).substitute; } if (source.flags & TypeFlags.IndexedAccess) { source = getSimplifiedType(source); @@ -11124,6 +11185,49 @@ namespace ts { } } } + else if (target.flags & TypeFlags.Conditional) { + // Simple check - if assignable to both the true and false types, it is assignable + let trueType = getTrueTypeFromConditionalType(target as ConditionalType); + let falseType = getFalseTypeFromConditionalType(target as ConditionalType); + if (result = isRelatedTo(source, trueType) && isRelatedTo(source, falseType)) { + errorInfo = saveErrorInfo; + return result; + } + + const root = (target as ConditionalType).root; + const conditional = target as ConditionalType; + if (root.trueType.flags & TypeFlags.Substitution) { + const sub = root.trueType as SubstitutionType; + trueType = getSubstitutionType( + trueType, + instantiateType(sub.substitute, conditional.combinedMapper || conditional.mapper), + instantiateTypes(sub.negatedTypes, conditional.combinedMapper || conditional.mapper || identityMapper) + ); + } + if (root.falseType.flags & TypeFlags.Substitution) { + const sub = root.falseType as SubstitutionType; + falseType = getSubstitutionType( + falseType, + instantiateType(sub.substitute, conditional.combinedMapper || conditional.mapper), + instantiateTypes(sub.negatedTypes, conditional.combinedMapper || conditional.mapper || identityMapper) + ); + } + if (root.isDistributive && source.flags & TypeFlags.Union) { + // If the source is a union, and the root is distributive, break up the union and distribute it, too + const types = (source as UnionType).types; + let result = Ternary.True; + for (const type of types) { + result &= isAssociatedWithOneConditionalTypeBranch(type, target as ConditionalType, trueType, falseType); + if (!result) { + return result; + } + } + return result; + } + else { + return isAssociatedWithOneConditionalTypeBranch(source, target as ConditionalType, trueType, falseType); + } + } else { if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (source).target === (target).target && !(getObjectFlags(source) & ObjectFlags.MarkerType || getObjectFlags(target) & ObjectFlags.MarkerType)) { @@ -11204,6 +11308,31 @@ namespace ts { return Ternary.False; } + function isAssociatedWithOneConditionalTypeBranch(source: Type, target: ConditionalType, trueType: Type, falseType: Type) { + // This is a complex check for relating the source to either branch of a conditional. + // Assume we have a + // * Target of type `{ x: T } extends { x: object } ? T : never` + // * Source of type `{ x: number }` + // In order to check this, we need to discover if the `source` type could be a result given the target. + // To do so, first we assume that the `true` branch will succeed. We draw an inference from the `source` to the `trueType`. + // This inference produces instantiations for the type variables in the `checkType` (the left hand side of the `extends` clause). + // We instantiate the check type with these inferences, then check if the conditional is true, asking "does the check type actually extend the extends type?" + // If so, the `source` is assignable to the `target`. If not, the same process can be used for the `false` branch, but using definitely-not-assignable for + // checking the relationship between the instantiated `check` and `extends` types instead. + // If neither case is true, the type is not assignable. + if (isRelatedTo(source, trueType)) { + if (conditionalTypeReverseInferenceSucceeds(source, target, trueType, assignableRelation)) { + return Ternary.True; + } + } + if (isRelatedTo(source, falseType)) { + if (!conditionalTypeReverseInferenceSucceeds(source, target, falseType, definitelyAssignableRelation)) { + return Ternary.True; + } + } + return Ternary.False; + } + // A type [P in S]: X is related to a type [Q in T]: Y if T is related to S and X' is // related to Y, where X' is an instantiation of X in which P is replaced with Q. Notice // that S and T are contra-variant whereas X and Y are co-variant. @@ -12437,11 +12566,12 @@ namespace ts { function createEmptyObjectTypeFromStringLiteral(type: Type) { const members = createSymbolTable(); forEachType(type, t => { - if (!(t.flags & TypeFlags.StringLiteral)) { + if (!isLiteralType(t)) { return; } - const name = escapeLeadingUnderscores((t as StringLiteralType).value); + const name = escapeLeadingUnderscores("" + (t as LiteralType).value); const literalProp = createSymbol(SymbolFlags.Property, name); + literalProp.nameType = t; literalProp.type = anyType; if (t.symbol) { literalProp.declarations = t.symbol.declarations; @@ -12971,11 +13101,13 @@ namespace ts { inference.inferredType = inferredType; - const constraint = getConstraintOfTypeParameter(inference.typeParameter); - if (constraint) { - const instantiatedConstraint = instantiateType(constraint, context); - if (!context.compareTypes(inferredType, getTypeWithThisArgument(instantiatedConstraint, inferredType))) { - inference.inferredType = inferredType = instantiatedConstraint; + if (!(context.flags & InferenceFlags.SkipConstraintCheck)) { + const constraint = getConstraintOfTypeParameter(inference.typeParameter); + if (constraint) { + const instantiatedConstraint = instantiateType(constraint, context); + if (!context.compareTypes(inferredType, getTypeWithThisArgument(instantiatedConstraint, inferredType))) { + inference.inferredType = inferredType = instantiatedConstraint; + } } } } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 726947111be0d..8b1264e95684e 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4064,8 +4064,9 @@ namespace ts { // Thus, if Foo has a 'string' constraint on its type parameter, T will satisfy it. Substitution // types disappear upon instantiation (just like type parameters). export interface SubstitutionType extends InstantiableType { - typeVariable: TypeVariable; // Target type variable + typeVariable: Type; // Target type variable substitute: Type; // Type to substitute for type parameter + negatedTypes?: Type[]; // Types proven that this type variables _doesn't_ extend } export const enum SignatureKind { @@ -4152,6 +4153,7 @@ namespace ts { InferUnionTypes = 1 << 0, // Infer union types for disjoint candidates (otherwise unknownType) NoDefault = 1 << 1, // Infer unknownType for no inferences (otherwise anyType or emptyObjectType) AnyDefault = 1 << 2, // Infer anyType for no inferences (otherwise emptyObjectType) + SkipConstraintCheck = 1 << 3, // Skip checking weather the inference is assignable to the constraint (otherwise is replaced with constraint) } /** diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index ba6ffcb80ac71..7872ffdf7cf58 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -2300,8 +2300,9 @@ declare namespace ts { resolvedFalseType?: Type; } interface SubstitutionType extends InstantiableType { - typeVariable: TypeVariable; + typeVariable: Type; substitute: Type; + negatedTypes?: Type[]; } enum SignatureKind { Call = 0, diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index c055400e628a4..9669f550c7862 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -2300,8 +2300,9 @@ declare namespace ts { resolvedFalseType?: Type; } interface SubstitutionType extends InstantiableType { - typeVariable: TypeVariable; + typeVariable: Type; substitute: Type; + negatedTypes?: Type[]; } enum SignatureKind { Call = 0, diff --git a/tests/baselines/reference/conditionalTypeGenericAssignability.errors.txt b/tests/baselines/reference/conditionalTypeGenericAssignability.errors.txt new file mode 100644 index 0000000000000..0297f4eab1116 --- /dev/null +++ b/tests/baselines/reference/conditionalTypeGenericAssignability.errors.txt @@ -0,0 +1,30 @@ +tests/cases/compiler/conditionalTypeGenericAssignability.ts(3,5): error TS2322: Type '0' is not assignable to type 'Extract'. +tests/cases/compiler/conditionalTypeGenericAssignability.ts(7,5): error TS2322: Type '"foo"' is not assignable to type 'Exclude'. +tests/cases/compiler/conditionalTypeGenericAssignability.ts(16,5): error TS2322: Type '{ y: { x: T; }; }' is not assignable to type '{ x: T; } extends { x: string; } ? { y: { x: T; }; } : never'. + + +==== tests/cases/compiler/conditionalTypeGenericAssignability.ts (3 errors) ==== + function f1(_a: T, b: Extract) { + b = "foo"; // succeeds + b = 0; // errors + ~ +!!! error TS2322: Type '0' is not assignable to type 'Extract'. + } + + function f2(_a: T, b: Exclude) { + b = "foo"; // errors + ~ +!!! error TS2322: Type '"foo"' is not assignable to type 'Exclude'. + b = 0; // succeeds + } + + function f3( + i: T & string, + j: T, + b: { x: T } extends { x: string } ? { y: { x: T } } : never) { + b = { y: { x: i } }; // success + b = { y: { x: j } }; // failure + ~ +!!! error TS2322: Type '{ y: { x: T; }; }' is not assignable to type '{ x: T; } extends { x: string; } ? { y: { x: T; }; } : never'. + } + \ No newline at end of file diff --git a/tests/baselines/reference/conditionalTypeGenericAssignability.js b/tests/baselines/reference/conditionalTypeGenericAssignability.js new file mode 100644 index 0000000000000..10d18af5f6507 --- /dev/null +++ b/tests/baselines/reference/conditionalTypeGenericAssignability.js @@ -0,0 +1,34 @@ +//// [conditionalTypeGenericAssignability.ts] +function f1(_a: T, b: Extract) { + b = "foo"; // succeeds + b = 0; // errors +} + +function f2(_a: T, b: Exclude) { + b = "foo"; // errors + b = 0; // succeeds +} + +function f3( + i: T & string, + j: T, + b: { x: T } extends { x: string } ? { y: { x: T } } : never) { + b = { y: { x: i } }; // success + b = { y: { x: j } }; // failure +} + + +//// [conditionalTypeGenericAssignability.js] +"use strict"; +function f1(_a, b) { + b = "foo"; // succeeds + b = 0; // errors +} +function f2(_a, b) { + b = "foo"; // errors + b = 0; // succeeds +} +function f3(i, j, b) { + b = { y: { x: i } }; // success + b = { y: { x: j } }; // failure +} diff --git a/tests/baselines/reference/conditionalTypeGenericAssignability.symbols b/tests/baselines/reference/conditionalTypeGenericAssignability.symbols new file mode 100644 index 0000000000000..b517bc4d3ba18 --- /dev/null +++ b/tests/baselines/reference/conditionalTypeGenericAssignability.symbols @@ -0,0 +1,71 @@ +=== tests/cases/compiler/conditionalTypeGenericAssignability.ts === +function f1(_a: T, b: Extract) { +>f1 : Symbol(f1, Decl(conditionalTypeGenericAssignability.ts, 0, 0)) +>T : Symbol(T, Decl(conditionalTypeGenericAssignability.ts, 0, 12)) +>foo : Symbol(foo, Decl(conditionalTypeGenericAssignability.ts, 0, 23)) +>0 : Symbol(0, Decl(conditionalTypeGenericAssignability.ts, 0, 37)) +>_a : Symbol(_a, Decl(conditionalTypeGenericAssignability.ts, 0, 52)) +>T : Symbol(T, Decl(conditionalTypeGenericAssignability.ts, 0, 12)) +>b : Symbol(b, Decl(conditionalTypeGenericAssignability.ts, 0, 58)) +>Extract : Symbol(Extract, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(conditionalTypeGenericAssignability.ts, 0, 12)) + + b = "foo"; // succeeds +>b : Symbol(b, Decl(conditionalTypeGenericAssignability.ts, 0, 58)) + + b = 0; // errors +>b : Symbol(b, Decl(conditionalTypeGenericAssignability.ts, 0, 58)) +} + +function f2(_a: T, b: Exclude) { +>f2 : Symbol(f2, Decl(conditionalTypeGenericAssignability.ts, 3, 1)) +>T : Symbol(T, Decl(conditionalTypeGenericAssignability.ts, 5, 12)) +>foo : Symbol(foo, Decl(conditionalTypeGenericAssignability.ts, 5, 23)) +>0 : Symbol(0, Decl(conditionalTypeGenericAssignability.ts, 5, 37)) +>_a : Symbol(_a, Decl(conditionalTypeGenericAssignability.ts, 5, 52)) +>T : Symbol(T, Decl(conditionalTypeGenericAssignability.ts, 5, 12)) +>b : Symbol(b, Decl(conditionalTypeGenericAssignability.ts, 5, 58)) +>Exclude : Symbol(Exclude, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(conditionalTypeGenericAssignability.ts, 5, 12)) + + b = "foo"; // errors +>b : Symbol(b, Decl(conditionalTypeGenericAssignability.ts, 5, 58)) + + b = 0; // succeeds +>b : Symbol(b, Decl(conditionalTypeGenericAssignability.ts, 5, 58)) +} + +function f3( +>f3 : Symbol(f3, Decl(conditionalTypeGenericAssignability.ts, 8, 1)) +>T : Symbol(T, Decl(conditionalTypeGenericAssignability.ts, 10, 12)) + + i: T & string, +>i : Symbol(i, Decl(conditionalTypeGenericAssignability.ts, 10, 39)) +>T : Symbol(T, Decl(conditionalTypeGenericAssignability.ts, 10, 12)) + + j: T, +>j : Symbol(j, Decl(conditionalTypeGenericAssignability.ts, 11, 18)) +>T : Symbol(T, Decl(conditionalTypeGenericAssignability.ts, 10, 12)) + + b: { x: T } extends { x: string } ? { y: { x: T } } : never) { +>b : Symbol(b, Decl(conditionalTypeGenericAssignability.ts, 12, 9)) +>x : Symbol(x, Decl(conditionalTypeGenericAssignability.ts, 13, 8)) +>T : Symbol(T, Decl(conditionalTypeGenericAssignability.ts, 10, 12)) +>x : Symbol(x, Decl(conditionalTypeGenericAssignability.ts, 13, 25)) +>y : Symbol(y, Decl(conditionalTypeGenericAssignability.ts, 13, 41)) +>x : Symbol(x, Decl(conditionalTypeGenericAssignability.ts, 13, 46)) +>T : Symbol(T, Decl(conditionalTypeGenericAssignability.ts, 10, 12)) + + b = { y: { x: i } }; // success +>b : Symbol(b, Decl(conditionalTypeGenericAssignability.ts, 12, 9)) +>y : Symbol(y, Decl(conditionalTypeGenericAssignability.ts, 14, 9)) +>x : Symbol(x, Decl(conditionalTypeGenericAssignability.ts, 14, 14)) +>i : Symbol(i, Decl(conditionalTypeGenericAssignability.ts, 10, 39)) + + b = { y: { x: j } }; // failure +>b : Symbol(b, Decl(conditionalTypeGenericAssignability.ts, 12, 9)) +>y : Symbol(y, Decl(conditionalTypeGenericAssignability.ts, 15, 9)) +>x : Symbol(x, Decl(conditionalTypeGenericAssignability.ts, 15, 14)) +>j : Symbol(j, Decl(conditionalTypeGenericAssignability.ts, 11, 18)) +} + diff --git a/tests/baselines/reference/conditionalTypeGenericAssignability.types b/tests/baselines/reference/conditionalTypeGenericAssignability.types new file mode 100644 index 0000000000000..b6ba2dcf783c1 --- /dev/null +++ b/tests/baselines/reference/conditionalTypeGenericAssignability.types @@ -0,0 +1,85 @@ +=== tests/cases/compiler/conditionalTypeGenericAssignability.ts === +function f1(_a: T, b: Extract) { +>f1 : (_a: T, b: Extract) => void +>T : T +>foo : unknown +>0 : unknown +>_a : T +>T : T +>b : Extract +>Extract : Extract +>T : T + + b = "foo"; // succeeds +>b = "foo" : "foo" +>b : Extract +>"foo" : "foo" + + b = 0; // errors +>b = 0 : 0 +>b : Extract +>0 : 0 +} + +function f2(_a: T, b: Exclude) { +>f2 : (_a: T, b: Exclude) => void +>T : T +>foo : unknown +>0 : unknown +>_a : T +>T : T +>b : Exclude +>Exclude : Exclude +>T : T + + b = "foo"; // errors +>b = "foo" : "foo" +>b : Exclude +>"foo" : "foo" + + b = 0; // succeeds +>b = 0 : 0 +>b : Exclude +>0 : 0 +} + +function f3( +>f3 : (i: T & string, j: T, b: { x: T; } extends { x: string; } ? { y: { x: T; }; } : never) => void +>T : T + + i: T & string, +>i : T & string +>T : T + + j: T, +>j : T +>T : T + + b: { x: T } extends { x: string } ? { y: { x: T } } : never) { +>b : { x: T; } extends { x: string; } ? { y: { x: T; }; } : never +>x : T +>T : T +>x : string +>y : { x: T; } +>x : T +>T : T + + b = { y: { x: i } }; // success +>b = { y: { x: i } } : { y: { x: T & string; }; } +>b : { x: T; } extends { x: string; } ? { y: { x: T; }; } : never +>{ y: { x: i } } : { y: { x: T & string; }; } +>y : { x: T & string; } +>{ x: i } : { x: T & string; } +>x : T & string +>i : T & string + + b = { y: { x: j } }; // failure +>b = { y: { x: j } } : { y: { x: T; }; } +>b : { x: T; } extends { x: string; } ? { y: { x: T; }; } : never +>{ y: { x: j } } : { y: { x: T; }; } +>y : { x: T; } +>{ x: j } : { x: T; } +>x : T +>j : T +} + diff --git a/tests/baselines/reference/inferTypes1.types b/tests/baselines/reference/inferTypes1.types index 3138341592d0d..ec3fdb170cdef 100644 --- a/tests/baselines/reference/inferTypes1.types +++ b/tests/baselines/reference/inferTypes1.types @@ -320,7 +320,7 @@ type T61 = infer A extends infer B ? infer C : infer D; // Error >D : D type T62 = U extends (infer U)[] ? U : U; // Error ->T62 : any +>T62 : {} | any >T : T >U : No type information available! >U : U diff --git a/tests/baselines/reference/keyofExtractConstrainsAsExpected.js b/tests/baselines/reference/keyofExtractConstrainsAsExpected.js new file mode 100644 index 0000000000000..f27148e1ed1c7 --- /dev/null +++ b/tests/baselines/reference/keyofExtractConstrainsAsExpected.js @@ -0,0 +1,12 @@ +//// [keyofExtractConstrainsAsExpected.ts] +type StringKeyof = Extract; + +type Whatever> = any; + +type WithoutFoo = Whatever<{ foo: string }, "foo">; // ok + +// no error on the following +type WithoutFooGeneric

= Whatever; + + +//// [keyofExtractConstrainsAsExpected.js] diff --git a/tests/baselines/reference/keyofExtractConstrainsAsExpected.symbols b/tests/baselines/reference/keyofExtractConstrainsAsExpected.symbols new file mode 100644 index 0000000000000..12f83eb8a8641 --- /dev/null +++ b/tests/baselines/reference/keyofExtractConstrainsAsExpected.symbols @@ -0,0 +1,27 @@ +=== tests/cases/compiler/keyofExtractConstrainsAsExpected.ts === +type StringKeyof = Extract; +>StringKeyof : Symbol(StringKeyof, Decl(keyofExtractConstrainsAsExpected.ts, 0, 0)) +>T : Symbol(T, Decl(keyofExtractConstrainsAsExpected.ts, 0, 17)) +>Extract : Symbol(Extract, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(keyofExtractConstrainsAsExpected.ts, 0, 17)) + +type Whatever> = any; +>Whatever : Symbol(Whatever, Decl(keyofExtractConstrainsAsExpected.ts, 0, 47)) +>T : Symbol(T, Decl(keyofExtractConstrainsAsExpected.ts, 2, 14)) +>K : Symbol(K, Decl(keyofExtractConstrainsAsExpected.ts, 2, 16)) +>StringKeyof : Symbol(StringKeyof, Decl(keyofExtractConstrainsAsExpected.ts, 0, 0)) +>T : Symbol(T, Decl(keyofExtractConstrainsAsExpected.ts, 2, 14)) + +type WithoutFoo = Whatever<{ foo: string }, "foo">; // ok +>WithoutFoo : Symbol(WithoutFoo, Decl(keyofExtractConstrainsAsExpected.ts, 2, 49)) +>Whatever : Symbol(Whatever, Decl(keyofExtractConstrainsAsExpected.ts, 0, 47)) +>foo : Symbol(foo, Decl(keyofExtractConstrainsAsExpected.ts, 4, 28)) + +// no error on the following +type WithoutFooGeneric

= Whatever; +>WithoutFooGeneric : Symbol(WithoutFooGeneric, Decl(keyofExtractConstrainsAsExpected.ts, 4, 51)) +>P : Symbol(P, Decl(keyofExtractConstrainsAsExpected.ts, 7, 23)) +>foo : Symbol(foo, Decl(keyofExtractConstrainsAsExpected.ts, 7, 34)) +>Whatever : Symbol(Whatever, Decl(keyofExtractConstrainsAsExpected.ts, 0, 47)) +>P : Symbol(P, Decl(keyofExtractConstrainsAsExpected.ts, 7, 23)) + diff --git a/tests/baselines/reference/keyofExtractConstrainsAsExpected.types b/tests/baselines/reference/keyofExtractConstrainsAsExpected.types new file mode 100644 index 0000000000000..ebda7a2810d8a --- /dev/null +++ b/tests/baselines/reference/keyofExtractConstrainsAsExpected.types @@ -0,0 +1,27 @@ +=== tests/cases/compiler/keyofExtractConstrainsAsExpected.ts === +type StringKeyof = Extract; +>StringKeyof : Extract +>T : T +>Extract : Extract +>T : T + +type Whatever> = any; +>Whatever : any +>T : T +>K : K +>StringKeyof : Extract +>T : T + +type WithoutFoo = Whatever<{ foo: string }, "foo">; // ok +>WithoutFoo : any +>Whatever : any +>foo : string + +// no error on the following +type WithoutFooGeneric

= Whatever; +>WithoutFooGeneric : any +>P : P +>foo : string +>Whatever : any +>P : P + diff --git a/tests/cases/compiler/conditionalTypeGenericAssignability.ts b/tests/cases/compiler/conditionalTypeGenericAssignability.ts new file mode 100644 index 0000000000000..5f330aa74eb27 --- /dev/null +++ b/tests/cases/compiler/conditionalTypeGenericAssignability.ts @@ -0,0 +1,18 @@ +// @strict: true +function f1(_a: T, b: Extract) { + b = "foo"; // succeeds + b = 0; // errors +} + +function f2(_a: T, b: Exclude) { + b = "foo"; // errors + b = 0; // succeeds +} + +function f3( + i: T & string, + j: T, + b: { x: T } extends { x: string } ? { y: { x: T } } : never) { + b = { y: { x: i } }; // success + b = { y: { x: j } }; // failure +} diff --git a/tests/cases/compiler/keyofExtractConstrainsAsExpected.ts b/tests/cases/compiler/keyofExtractConstrainsAsExpected.ts new file mode 100644 index 0000000000000..dd29a617636f3 --- /dev/null +++ b/tests/cases/compiler/keyofExtractConstrainsAsExpected.ts @@ -0,0 +1,8 @@ +type StringKeyof = Extract; + +type Whatever> = any; + +type WithoutFoo = Whatever<{ foo: string }, "foo">; // ok + +// no error on the following +type WithoutFooGeneric

= Whatever;