From 590e4d59f9f6ebea7b942cd2f012f9261dfcc36e Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Fri, 8 Jun 2018 16:11:10 -0700 Subject: [PATCH 1/4] Add missing conditional type target relationship --- src/compiler/checker.ts | 105 ++++++++++++++++++ .../keyofExtractConstrainsAsExpected.js | 12 ++ .../keyofExtractConstrainsAsExpected.symbols | 27 +++++ .../keyofExtractConstrainsAsExpected.types | 27 +++++ .../keyofExtractConstrainsAsExpected.ts | 8 ++ 5 files changed, 179 insertions(+) create mode 100644 tests/baselines/reference/keyofExtractConstrainsAsExpected.js create mode 100644 tests/baselines/reference/keyofExtractConstrainsAsExpected.symbols create mode 100644 tests/baselines/reference/keyofExtractConstrainsAsExpected.types create mode 100644 tests/cases/compiler/keyofExtractConstrainsAsExpected.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ce1ee54edcde4..8d8712cc2b418 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -10396,6 +10396,48 @@ 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) { + let checkType = target.checkType; + let mapper = identityMapper; + if (target.root.outerTypeParameters) { + // First, infer from source to the inference target + const params = collectTypeParameters(inferenceTarget); + const context = createInferenceContext(params, /*signature*/ undefined, InferenceFlags.None); + 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. @@ -11124,6 +11166,31 @@ namespace ts { } } } + else if (target.flags & TypeFlags.Conditional) { + // Simple check - if assignable to both the true and false types, it is assignable + const trueType = getTrueTypeFromConditionalType(target as ConditionalType); + const falseType = getFalseTypeFromConditionalType(target as ConditionalType); + if (result = isRelatedTo(source, trueType) && isRelatedTo(source, falseType)) { + errorInfo = saveErrorInfo; + return result; + } + + if ((target as ConditionalType).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 +11271,44 @@ 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; + } + } + // TODO: The following `false` branch logic is good, but type variables in `false` branches do not currently + // retain their negated constraint, resulting in false positives here; for example: + // ``` + // @strict: true + // function f(x: T["x"], y: NonNullable) { + // y = x; + // // Should be an error, but if we do the below, we find that yes, `undefined` is assignable to `T["x"]` + // // because we've "forgotten" that we already verified that `T["x"]` does _not_ extend `null | undefined` + // // in this position and that this should _not_ succeed. In effect, we need a substitution type with + // // negatypes to come into play here. If/when that comes about, we should uncomment the following code + // // to allow types compatible with the `false` branch of a conditional to be assignable to it. + // } + // ``` + //else 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. 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/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; From 88623add3ca4ddf4cc57136e2d101f6a72bfe37c Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Fri, 8 Jun 2018 16:41:55 -0700 Subject: [PATCH 2/4] Track falsified constraints on substitution types --- src/compiler/checker.ts | 47 ++++++++++--------- src/compiler/types.ts | 1 + .../reference/api/tsserverlibrary.d.ts | 1 + tests/baselines/reference/api/typescript.d.ts | 1 + 4 files changed, 28 insertions(+), 22 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 8d8712cc2b418..5079a54e11ee1 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: TypeVariable, substitute: Type, negaConstraints: Type[] | undefined) { const result = createType(TypeFlags.Substitution); result.typeVariable = typeVariable; result.substitute = substitute; + result.negatedTypes = negaConstraints; return result; } @@ -7974,17 +7975,23 @@ namespace ts { function getConstrainedTypeVariable(typeVariable: TypeVariable, 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 { @@ -10417,6 +10424,7 @@ namespace ts { function conditionalTypeReverseInferenceSucceeds(source: Type, target: ConditionalType, inferenceTarget: Type, relation: Map) { 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); @@ -10571,6 +10579,14 @@ namespace ts { source = relation === definitelyAssignableRelation ? (source).typeVariable : (source).substitute; } if (target.flags & TypeFlags.Substitution) { + const negaTypes = (target as SubstitutionType).negatedTypes; + if (negaTypes) { + for (const type of negaTypes) { + if (isRelatedTo(source, type)) { + return Ternary.False; + } + } + } target = (target).typeVariable; } if (source.flags & TypeFlags.IndexedAccess) { @@ -11288,24 +11304,11 @@ namespace ts { return Ternary.True; } } - // TODO: The following `false` branch logic is good, but type variables in `false` branches do not currently - // retain their negated constraint, resulting in false positives here; for example: - // ``` - // @strict: true - // function f(x: T["x"], y: NonNullable) { - // y = x; - // // Should be an error, but if we do the below, we find that yes, `undefined` is assignable to `T["x"]` - // // because we've "forgotten" that we already verified that `T["x"]` does _not_ extend `null | undefined` - // // in this position and that this should _not_ succeed. In effect, we need a substitution type with - // // negatypes to come into play here. If/when that comes about, we should uncomment the following code - // // to allow types compatible with the `false` branch of a conditional to be assignable to it. - // } - // ``` - //else if (isRelatedTo(source, falseType)) { - // if (!conditionalTypeReverseInferenceSucceeds(source, target, falseType, definitelyAssignableRelation)) { - // return Ternary.True; - // } - //} + else if (isRelatedTo(source, falseType)) { + if (!conditionalTypeReverseInferenceSucceeds(source, target, falseType, definitelyAssignableRelation)) { + return Ternary.True; + } + } return Ternary.False; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 726947111be0d..ca623acea7c59 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4066,6 +4066,7 @@ namespace ts { export interface SubstitutionType extends InstantiableType { typeVariable: TypeVariable; // 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 { diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index ba6ffcb80ac71..5aba9b46a0e9f 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -2302,6 +2302,7 @@ declare namespace ts { interface SubstitutionType extends InstantiableType { typeVariable: TypeVariable; 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..ed618c451b931 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -2302,6 +2302,7 @@ declare namespace ts { interface SubstitutionType extends InstantiableType { typeVariable: TypeVariable; substitute: Type; + negatedTypes?: Type[]; } enum SignatureKind { Call = 0, From aa2709f15d9d9fa5500d14072fb2651c515c27a1 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Fri, 8 Jun 2018 18:58:52 -0700 Subject: [PATCH 3/4] Handle propagating substitution types to retain constraints on positions through instantiation --- src/compiler/checker.ts | 90 ++++++++++++------- src/compiler/types.ts | 3 +- .../reference/api/tsserverlibrary.d.ts | 2 +- tests/baselines/reference/api/typescript.d.ts | 2 +- ...itionalTypeGenericAssignability.errors.txt | 19 ++++ .../conditionalTypeGenericAssignability.js | 22 +++++ ...onditionalTypeGenericAssignability.symbols | 37 ++++++++ .../conditionalTypeGenericAssignability.types | 45 ++++++++++ tests/baselines/reference/inferTypes1.types | 2 +- .../conditionalTypeGenericAssignability.ts | 10 +++ 10 files changed, 195 insertions(+), 37 deletions(-) create mode 100644 tests/baselines/reference/conditionalTypeGenericAssignability.errors.txt create mode 100644 tests/baselines/reference/conditionalTypeGenericAssignability.js create mode 100644 tests/baselines/reference/conditionalTypeGenericAssignability.symbols create mode 100644 tests/baselines/reference/conditionalTypeGenericAssignability.types create mode 100644 tests/cases/compiler/conditionalTypeGenericAssignability.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 5079a54e11ee1..05f141ea3794c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7955,7 +7955,7 @@ namespace ts { } } - function getSubstitutionType(typeVariable: TypeVariable, substitute: Type, negaConstraints: Type[] | undefined) { + function getSubstitutionType(typeVariable: Type, substitute: Type, negaConstraints: Type[] | undefined) { const result = createType(TypeFlags.Substitution); result.typeVariable = typeVariable; result.substitute = substitute; @@ -7967,13 +7967,13 @@ 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) { @@ -8076,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; } @@ -8092,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; } @@ -8245,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; } @@ -8300,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; } @@ -8550,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; } @@ -8693,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; } @@ -8768,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 @@ -8994,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; } @@ -9006,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); @@ -9133,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); @@ -9217,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 } } @@ -9239,7 +9239,7 @@ namespace ts { if (isJSDocTypeLiteral(node) && node.isArrayType) { type = createArrayType(type); } - links.resolvedType = type; + links.resolvedType = getConstrainedTypeVariable(type, node); } } return links.resolvedType; @@ -9447,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; } @@ -10422,13 +10422,16 @@ namespace ts { * @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); - const context = createInferenceContext(params, /*signature*/ undefined, InferenceFlags.None); + // 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 @@ -10587,7 +10590,7 @@ namespace ts { } } } - target = (target).typeVariable; + target = (target).substitute; } if (source.flags & TypeFlags.IndexedAccess) { source = getSimplifiedType(source); @@ -11184,14 +11187,32 @@ namespace ts { } else if (target.flags & TypeFlags.Conditional) { // Simple check - if assignable to both the true and false types, it is assignable - const trueType = getTrueTypeFromConditionalType(target as ConditionalType); - const falseType = getFalseTypeFromConditionalType(target as ConditionalType); + let trueType = getTrueTypeFromConditionalType(target as ConditionalType); + let falseType = getFalseTypeFromConditionalType(target as ConditionalType); if (result = isRelatedTo(source, trueType) && isRelatedTo(source, falseType)) { errorInfo = saveErrorInfo; return result; } - if ((target as ConditionalType).root.isDistributive && source.flags & TypeFlags.Union) { + 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; @@ -11287,7 +11308,7 @@ namespace ts { return Ternary.False; } - function isAssociatedWithOneConditionalTypeBranch(source: Type, target: ConditionalType, trueType: Type, _falseType: Type) { + 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` @@ -11304,7 +11325,7 @@ namespace ts { return Ternary.True; } } - else if (isRelatedTo(source, falseType)) { + if (isRelatedTo(source, falseType)) { if (!conditionalTypeReverseInferenceSucceeds(source, target, falseType, definitelyAssignableRelation)) { return Ternary.True; } @@ -12545,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; @@ -13079,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 ca623acea7c59..8b1264e95684e 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4064,7 +4064,7 @@ 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 } @@ -4153,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 5aba9b46a0e9f..7872ffdf7cf58 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -2300,7 +2300,7 @@ declare namespace ts { resolvedFalseType?: Type; } interface SubstitutionType extends InstantiableType { - typeVariable: TypeVariable; + typeVariable: Type; substitute: Type; negatedTypes?: Type[]; } diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index ed618c451b931..9669f550c7862 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -2300,7 +2300,7 @@ declare namespace ts { resolvedFalseType?: Type; } interface SubstitutionType extends InstantiableType { - typeVariable: TypeVariable; + typeVariable: Type; substitute: Type; negatedTypes?: Type[]; } diff --git a/tests/baselines/reference/conditionalTypeGenericAssignability.errors.txt b/tests/baselines/reference/conditionalTypeGenericAssignability.errors.txt new file mode 100644 index 0000000000000..0da6311765c9e --- /dev/null +++ b/tests/baselines/reference/conditionalTypeGenericAssignability.errors.txt @@ -0,0 +1,19 @@ +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 (2 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 + } + \ 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..dd859992fdf48 --- /dev/null +++ b/tests/baselines/reference/conditionalTypeGenericAssignability.js @@ -0,0 +1,22 @@ +//// [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 +} + + +//// [conditionalTypeGenericAssignability.js] +"use strict"; +function f1(_a, b) { + b = "foo"; // succeeds + b = 0; // errors +} +function f2(_a, b) { + b = "foo"; // errors + b = 0; // succeeds +} diff --git a/tests/baselines/reference/conditionalTypeGenericAssignability.symbols b/tests/baselines/reference/conditionalTypeGenericAssignability.symbols new file mode 100644 index 0000000000000..e0e4f9cc1f9fc --- /dev/null +++ b/tests/baselines/reference/conditionalTypeGenericAssignability.symbols @@ -0,0 +1,37 @@ +=== 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)) +} + diff --git a/tests/baselines/reference/conditionalTypeGenericAssignability.types b/tests/baselines/reference/conditionalTypeGenericAssignability.types new file mode 100644 index 0000000000000..dc2faa341385d --- /dev/null +++ b/tests/baselines/reference/conditionalTypeGenericAssignability.types @@ -0,0 +1,45 @@ +=== 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 +} + 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/cases/compiler/conditionalTypeGenericAssignability.ts b/tests/cases/compiler/conditionalTypeGenericAssignability.ts new file mode 100644 index 0000000000000..eb73c79b092d0 --- /dev/null +++ b/tests/cases/compiler/conditionalTypeGenericAssignability.ts @@ -0,0 +1,10 @@ +// @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 +} From 447d3a978c8832f02ad43da3686d8d1427ec5035 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Fri, 8 Jun 2018 19:19:55 -0700 Subject: [PATCH 4/4] include deep matching example --- ...itionalTypeGenericAssignability.errors.txt | 13 +++++- .../conditionalTypeGenericAssignability.js | 12 ++++++ ...onditionalTypeGenericAssignability.symbols | 34 ++++++++++++++++ .../conditionalTypeGenericAssignability.types | 40 +++++++++++++++++++ .../conditionalTypeGenericAssignability.ts | 8 ++++ 5 files changed, 106 insertions(+), 1 deletion(-) diff --git a/tests/baselines/reference/conditionalTypeGenericAssignability.errors.txt b/tests/baselines/reference/conditionalTypeGenericAssignability.errors.txt index 0da6311765c9e..0297f4eab1116 100644 --- a/tests/baselines/reference/conditionalTypeGenericAssignability.errors.txt +++ b/tests/baselines/reference/conditionalTypeGenericAssignability.errors.txt @@ -1,8 +1,9 @@ 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 (2 errors) ==== +==== tests/cases/compiler/conditionalTypeGenericAssignability.ts (3 errors) ==== function f1(_a: T, b: Extract) { b = "foo"; // succeeds b = 0; // errors @@ -16,4 +17,14 @@ tests/cases/compiler/conditionalTypeGenericAssignability.ts(7,5): error TS2322: !!! 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 index dd859992fdf48..10d18af5f6507 100644 --- a/tests/baselines/reference/conditionalTypeGenericAssignability.js +++ b/tests/baselines/reference/conditionalTypeGenericAssignability.js @@ -8,6 +8,14 @@ function f2(_a: T, b: Exclude( + 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] @@ -20,3 +28,7 @@ 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 index e0e4f9cc1f9fc..b517bc4d3ba18 100644 --- a/tests/baselines/reference/conditionalTypeGenericAssignability.symbols +++ b/tests/baselines/reference/conditionalTypeGenericAssignability.symbols @@ -35,3 +35,37 @@ function f2(_a: T, b: Excludeb : 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 index dc2faa341385d..b6ba2dcf783c1 100644 --- a/tests/baselines/reference/conditionalTypeGenericAssignability.types +++ b/tests/baselines/reference/conditionalTypeGenericAssignability.types @@ -43,3 +43,43 @@ function f2(_a: T, b: Exclude0 : 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/cases/compiler/conditionalTypeGenericAssignability.ts b/tests/cases/compiler/conditionalTypeGenericAssignability.ts index eb73c79b092d0..5f330aa74eb27 100644 --- a/tests/cases/compiler/conditionalTypeGenericAssignability.ts +++ b/tests/cases/compiler/conditionalTypeGenericAssignability.ts @@ -8,3 +8,11 @@ function f2(_a: T, b: Exclude( + 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 +}