Merge pull request #5949 from Microsoft/typeParametersAsConstraints

Type parameters as constraints
This commit is contained in:
Anders Hejlsberg
2015-12-10 16:53:59 -08:00
178 changed files with 12662 additions and 3206 deletions
+97 -113
View File
@@ -4162,17 +4162,40 @@ namespace ts {
: undefined;
}
function getConstraintOfTypeParameter(type: TypeParameter): Type {
if (!type.constraint) {
if (type.target) {
const targetConstraint = getConstraintOfTypeParameter(type.target);
type.constraint = targetConstraint ? instantiateType(targetConstraint, type.mapper) : noConstraintType;
function getConstraintDeclaration(type: TypeParameter) {
return (<TypeParameterDeclaration>getDeclarationOfKind(type.symbol, SyntaxKind.TypeParameter)).constraint;
}
function hasConstraintReferenceTo(type: Type, target: TypeParameter): boolean {
let checked: Type[];
while (type && type.flags & TypeFlags.TypeParameter && !contains(checked, type)) {
if (type === target) {
return true;
}
(checked || (checked = [])).push(type);
const constraintDeclaration = getConstraintDeclaration(<TypeParameter>type);
type = constraintDeclaration && getTypeFromTypeNode(constraintDeclaration);
}
return false;
}
function getConstraintOfTypeParameter(typeParameter: TypeParameter): Type {
if (!typeParameter.constraint) {
if (typeParameter.target) {
const targetConstraint = getConstraintOfTypeParameter(typeParameter.target);
typeParameter.constraint = targetConstraint ? instantiateType(targetConstraint, typeParameter.mapper) : noConstraintType;
}
else {
type.constraint = getTypeFromTypeNode((<TypeParameterDeclaration>getDeclarationOfKind(type.symbol, SyntaxKind.TypeParameter)).constraint);
const constraintDeclaration = getConstraintDeclaration(typeParameter);
let constraint = getTypeFromTypeNode(constraintDeclaration);
if (hasConstraintReferenceTo(constraint, typeParameter)) {
error(constraintDeclaration, Diagnostics.Type_parameter_0_has_a_circular_constraint, typeToString(typeParameter));
constraint = unknownType;
}
typeParameter.constraint = constraint;
}
}
return type.constraint === noConstraintType ? undefined : type.constraint;
return typeParameter.constraint === noConstraintType ? undefined : typeParameter.constraint;
}
function getParentSymbolOfTypeParameter(typeParameter: TypeParameter): Symbol {
@@ -4224,55 +4247,6 @@ namespace ts {
return type;
}
function isTypeParameterReferenceIllegalInConstraint(typeReferenceNode: TypeReferenceNode | ExpressionWithTypeArguments, typeParameterSymbol: Symbol): boolean {
const links = getNodeLinks(typeReferenceNode);
if (links.isIllegalTypeReferenceInConstraint !== undefined) {
return links.isIllegalTypeReferenceInConstraint;
}
// bubble up to the declaration
let currentNode: Node = typeReferenceNode;
// forEach === exists
while (!forEach(typeParameterSymbol.declarations, d => d.parent === currentNode.parent)) {
currentNode = currentNode.parent;
}
// if last step was made from the type parameter this means that path has started somewhere in constraint which is illegal
links.isIllegalTypeReferenceInConstraint = currentNode.kind === SyntaxKind.TypeParameter;
return links.isIllegalTypeReferenceInConstraint;
}
function checkTypeParameterHasIllegalReferencesInConstraint(typeParameter: TypeParameterDeclaration): void {
let typeParameterSymbol: Symbol;
function check(n: Node): void {
if (n.kind === SyntaxKind.TypeReference && (<TypeReferenceNode>n).typeName.kind === SyntaxKind.Identifier) {
const links = getNodeLinks(n);
if (links.isIllegalTypeReferenceInConstraint === undefined) {
const symbol = resolveName(typeParameter, (<Identifier>(<TypeReferenceNode>n).typeName).text, SymbolFlags.Type, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined);
if (symbol && (symbol.flags & SymbolFlags.TypeParameter)) {
// TypeScript 1.0 spec (April 2014): 3.4.1
// Type parameters declared in a particular type parameter list
// may not be referenced in constraints in that type parameter list
// symbol.declaration.parent === typeParameter.parent
// -> typeParameter and symbol.declaration originate from the same type parameter list
// -> illegal for all declarations in symbol
// forEach === exists
links.isIllegalTypeReferenceInConstraint = forEach(symbol.declarations, d => d.parent === typeParameter.parent);
}
}
if (links.isIllegalTypeReferenceInConstraint) {
error(typeParameter, Diagnostics.Constraint_of_a_type_parameter_cannot_reference_any_type_parameter_from_the_same_type_parameter_list);
}
}
forEachChild(n, check);
}
if (typeParameter.constraint) {
typeParameterSymbol = getSymbolOfNode(typeParameter);
check(typeParameter.constraint);
}
}
// Get type from reference to class or interface
function getTypeFromClassOrInterfaceReference(node: TypeReferenceNode | ExpressionWithTypeArguments, symbol: Symbol): Type {
const type = <InterfaceType>getDeclaredTypeOfSymbol(symbol);
@@ -4319,13 +4293,6 @@ namespace ts {
// Get type from reference to named type that cannot be generic (enum or type parameter)
function getTypeFromNonGenericTypeReference(node: TypeReferenceNode | ExpressionWithTypeArguments, symbol: Symbol): Type {
if (symbol.flags & SymbolFlags.TypeParameter && isTypeParameterReferenceIllegalInConstraint(node, symbol)) {
// TypeScript 1.0 spec (April 2014): 3.4.1
// Type parameters declared in a particular type parameter list
// may not be referenced in constraints in that type parameter list
// Implementation: such type references are resolved to 'unknown' type that usually denotes error
return unknownType;
}
if (node.typeArguments) {
error(node, Diagnostics.Type_0_is_not_generic, symbolToString(symbol));
return unknownType;
@@ -4791,19 +4758,22 @@ namespace ts {
};
}
function createInferenceMapper(context: InferenceContext): TypeMapper {
const mapper: TypeMapper = t => {
for (let i = 0; i < context.typeParameters.length; i++) {
if (t === context.typeParameters[i]) {
context.inferences[i].isFixed = true;
return getInferredType(context, i);
function getInferenceMapper(context: InferenceContext): TypeMapper {
if (!context.mapper) {
const mapper: TypeMapper = t => {
const typeParameters = context.typeParameters;
for (let i = 0; i < typeParameters.length; i++) {
if (t === typeParameters[i]) {
context.inferences[i].isFixed = true;
return getInferredType(context, i);
}
}
}
return t;
};
mapper.context = context;
return mapper;
return t;
};
mapper.context = context;
context.mapper = mapper;
}
return context.mapper;
}
function identityMapper(type: Type): Type {
@@ -4814,16 +4784,10 @@ namespace ts {
return t => instantiateType(mapper1(t), mapper2);
}
function instantiateTypeParameter(typeParameter: TypeParameter, mapper: TypeMapper): TypeParameter {
function cloneTypeParameter(typeParameter: TypeParameter): TypeParameter {
const result = <TypeParameter>createType(TypeFlags.TypeParameter);
result.symbol = typeParameter.symbol;
if (typeParameter.constraint) {
result.constraint = instantiateType(typeParameter.constraint, mapper);
}
else {
result.target = typeParameter;
result.mapper = mapper;
}
result.target = typeParameter;
return result;
}
@@ -4847,8 +4811,14 @@ namespace ts {
function instantiateSignature(signature: Signature, mapper: TypeMapper, eraseTypeParameters?: boolean): Signature {
let freshTypeParameters: TypeParameter[];
if (signature.typeParameters && !eraseTypeParameters) {
freshTypeParameters = instantiateList(signature.typeParameters, mapper, instantiateTypeParameter);
// First create a fresh set of type parameters, then include a mapping from the old to the
// new type parameters in the mapper function. Finally store this mapper in the new type
// parameters such that we can use it when instantiating constraints.
freshTypeParameters = map(signature.typeParameters, cloneTypeParameter);
mapper = combineTypeMappers(createTypeMapper(signature.typeParameters, freshTypeParameters), mapper);
for (const tp of freshTypeParameters) {
tp.mapper = mapper;
}
}
const result = createSignature(signature.declaration, freshTypeParameters,
instantiateList(signature.parameters, mapper, instantiateSymbol),
@@ -5345,8 +5315,9 @@ namespace ts {
if (sources.length !== targets.length && relation === identityRelation) {
return Ternary.False;
}
const length = sources.length <= targets.length ? sources.length : targets.length;
let result = Ternary.True;
for (let i = 0; i < targets.length; i++) {
for (let i = 0; i < length; i++) {
const related = isRelatedTo(sources[i], targets[i], reportErrors);
if (!related) {
return Ternary.False;
@@ -5361,11 +5332,11 @@ namespace ts {
// Third, check if both types are part of deeply nested chains of generic type instantiations and if so assume the types are
// equal and infinitely expanding. Fourth, if we have reached a depth of 100 nested comparisons, assume we have runaway recursion
// and issue an error. Otherwise, actually compare the structure of the two types.
function objectTypeRelatedTo(apparentSource: Type, originalSource: Type, target: Type, reportErrors: boolean): Ternary {
function objectTypeRelatedTo(source: Type, originalSource: Type, target: Type, reportErrors: boolean): Ternary {
if (overflow) {
return Ternary.False;
}
const id = relation !== identityRelation || apparentSource.id < target.id ? apparentSource.id + "," + target.id : target.id + "," + apparentSource.id;
const id = relation !== identityRelation || source.id < target.id ? source.id + "," + target.id : target.id + "," + source.id;
const related = relation[id];
if (related !== undefined) {
if (elaborateErrors && related === RelationComparisonResult.Failed) {
@@ -5395,28 +5366,28 @@ namespace ts {
maybeStack = [];
expandingFlags = 0;
}
sourceStack[depth] = apparentSource;
sourceStack[depth] = source;
targetStack[depth] = target;
maybeStack[depth] = {};
maybeStack[depth][id] = RelationComparisonResult.Succeeded;
depth++;
const saveExpandingFlags = expandingFlags;
if (!(expandingFlags & 1) && isDeeplyNestedGeneric(apparentSource, sourceStack, depth)) expandingFlags |= 1;
if (!(expandingFlags & 1) && isDeeplyNestedGeneric(source, sourceStack, depth)) expandingFlags |= 1;
if (!(expandingFlags & 2) && isDeeplyNestedGeneric(target, targetStack, depth)) expandingFlags |= 2;
let result: Ternary;
if (expandingFlags === 3) {
result = Ternary.Maybe;
}
else {
result = propertiesRelatedTo(apparentSource, target, reportErrors);
result = propertiesRelatedTo(source, target, reportErrors);
if (result) {
result &= signaturesRelatedTo(apparentSource, target, SignatureKind.Call, reportErrors);
result &= signaturesRelatedTo(source, target, SignatureKind.Call, reportErrors);
if (result) {
result &= signaturesRelatedTo(apparentSource, target, SignatureKind.Construct, reportErrors);
result &= signaturesRelatedTo(source, target, SignatureKind.Construct, reportErrors);
if (result) {
result &= stringIndexTypesRelatedTo(apparentSource, originalSource, target, reportErrors);
result &= stringIndexTypesRelatedTo(source, originalSource, target, reportErrors);
if (result) {
result &= numberIndexTypesRelatedTo(apparentSource, originalSource, target, reportErrors);
result &= numberIndexTypesRelatedTo(source, originalSource, target, reportErrors);
}
}
}
@@ -6428,11 +6399,17 @@ namespace ts {
inferredType = emptyObjectType;
inferenceSucceeded = true;
}
context.inferredTypes[index] = inferredType;
// Only do the constraint check if inference succeeded (to prevent cascading errors)
if (inferenceSucceeded) {
const constraint = getConstraintOfTypeParameter(context.typeParameters[index]);
inferredType = constraint && !isTypeAssignableTo(inferredType, constraint) ? constraint : inferredType;
if (constraint) {
const instantiatedConstraint = instantiateType(constraint, getInferenceMapper(context));
if (!isTypeAssignableTo(inferredType, getTypeWithThisArgument(instantiatedConstraint, inferredType))) {
context.inferredTypes[index] = inferredType = instantiatedConstraint;
}
}
}
else if (context.failedTypeParameterIndex === undefined || context.failedTypeParameterIndex > index) {
// If inference failed, it is necessary to record the index of the failed type parameter (the one we are on).
@@ -6440,7 +6417,6 @@ namespace ts {
// So if this failure is on preceding type parameter, this type parameter is the new failure index.
context.failedTypeParameterIndex = index;
}
context.inferredTypes[index] = inferredType;
}
return inferredType;
}
@@ -6449,7 +6425,6 @@ namespace ts {
for (let i = 0; i < context.inferredTypes.length; i++) {
getInferredType(context, i);
}
return context.inferredTypes;
}
@@ -8875,7 +8850,7 @@ namespace ts {
function inferTypeArguments(node: CallLikeExpression, signature: Signature, args: Expression[], excludeArgument: boolean[], context: InferenceContext): void {
const typeParameters = signature.typeParameters;
const inferenceMapper = createInferenceMapper(context);
const inferenceMapper = getInferenceMapper(context);
// Clear out all the inference results from the last time inferTypeArguments was called on this context
for (let i = 0; i < typeParameters.length; i++) {
@@ -8941,14 +8916,11 @@ namespace ts {
getInferredTypes(context);
}
function checkTypeArguments(signature: Signature, typeArguments: TypeNode[], typeArgumentResultTypes: Type[], reportErrors: boolean, headMessage?: DiagnosticMessage): boolean {
function checkTypeArguments(signature: Signature, typeArgumentNodes: TypeNode[], typeArgumentTypes: Type[], reportErrors: boolean, headMessage?: DiagnosticMessage): boolean {
const typeParameters = signature.typeParameters;
let typeArgumentsAreAssignable = true;
let mapper: TypeMapper;
for (let i = 0; i < typeParameters.length; i++) {
const typeArgNode = typeArguments[i];
const typeArgument = getTypeFromTypeNode(typeArgNode);
// Do not push on this array! It has a preallocated length
typeArgumentResultTypes[i] = typeArgument;
if (typeArgumentsAreAssignable /* so far */) {
const constraint = getConstraintOfTypeParameter(typeParameters[i]);
if (constraint) {
@@ -8958,17 +8930,19 @@ namespace ts {
errorInfo = chainDiagnosticMessages(errorInfo, typeArgumentHeadMessage);
typeArgumentHeadMessage = headMessage;
}
if (!mapper) {
mapper = createTypeMapper(typeParameters, typeArgumentTypes);
}
const typeArgument = typeArgumentTypes[i];
typeArgumentsAreAssignable = checkTypeAssignableTo(
typeArgument,
constraint,
reportErrors ? typeArgNode : undefined,
getTypeWithThisArgument(instantiateType(constraint, mapper), typeArgument),
reportErrors ? typeArgumentNodes[i] : undefined,
typeArgumentHeadMessage,
errorInfo);
}
}
}
return typeArgumentsAreAssignable;
}
@@ -9424,7 +9398,8 @@ namespace ts {
}
else if (candidateForTypeArgumentError) {
if (!isTaggedTemplate && !isDecorator && typeArguments) {
checkTypeArguments(candidateForTypeArgumentError, (<CallExpression>node).typeArguments, [], /*reportErrors*/ true, headMessage);
const typeArguments = (<CallExpression>node).typeArguments;
checkTypeArguments(candidateForTypeArgumentError, typeArguments, map(typeArguments, getTypeFromTypeNode), /*reportErrors*/ true, headMessage);
}
else {
Debug.assert(resultOfFailedInference.failedTypeParameterIndex >= 0);
@@ -9491,7 +9466,7 @@ namespace ts {
if (candidate.typeParameters) {
let typeArgumentTypes: Type[];
if (typeArguments) {
typeArgumentTypes = new Array<Type>(candidate.typeParameters.length);
typeArgumentTypes = map(typeArguments, getTypeFromTypeNode);
typeArgumentsAreValid = checkTypeArguments(candidate, typeArguments, typeArgumentTypes, /*reportErrors*/ false);
}
else {
@@ -11023,11 +10998,10 @@ namespace ts {
}
checkSourceElement(node.constraint);
getConstraintOfTypeParameter(getDeclaredTypeOfTypeParameter(getSymbolOfNode(node)));
if (produceDiagnostics) {
checkTypeParameterHasIllegalReferencesInConstraint(node);
checkTypeNameIsReserved(node.name, Diagnostics.Type_parameter_name_cannot_be_0);
}
// TODO: Check multiple declarations are identical
}
function checkParameter(node: ParameterDeclaration) {
@@ -11452,13 +11426,23 @@ namespace ts {
checkDecorators(node);
}
function checkTypeArgumentConstraints(typeParameters: TypeParameter[], typeArguments: TypeNode[]): boolean {
function checkTypeArgumentConstraints(typeParameters: TypeParameter[], typeArgumentNodes: TypeNode[]): boolean {
let typeArguments: Type[];
let mapper: TypeMapper;
let result = true;
for (let i = 0; i < typeParameters.length; i++) {
const constraint = getConstraintOfTypeParameter(typeParameters[i]);
if (constraint) {
if (!typeArguments) {
typeArguments = map(typeArgumentNodes, getTypeFromTypeNode);
mapper = createTypeMapper(typeParameters, typeArguments);
}
const typeArgument = typeArguments[i];
result = result && checkTypeAssignableTo(getTypeFromTypeNode(typeArgument), constraint, typeArgument, Diagnostics.Type_0_does_not_satisfy_the_constraint_1);
result = result && checkTypeAssignableTo(
typeArgument,
getTypeWithThisArgument(instantiateType(constraint, mapper), typeArgument),
typeArgumentNodes[i],
Diagnostics.Type_0_does_not_satisfy_the_constraint_1);
}
}
return result;
+3 -3
View File
@@ -1309,6 +1309,9 @@ namespace ts {
}
function emitSignatureDeclaration(node: SignatureDeclaration) {
const prevEnclosingDeclaration = enclosingDeclaration;
enclosingDeclaration = node;
// Construct signature or constructor type write new Signature
if (node.kind === SyntaxKind.ConstructSignature || node.kind === SyntaxKind.ConstructorType) {
write("new ");
@@ -1321,9 +1324,6 @@ namespace ts {
write("(");
}
const prevEnclosingDeclaration = enclosingDeclaration;
enclosingDeclaration = node;
// Parameters
emitCommaList(node.parameters, emitParameterDeclaration);
+1 -1
View File
@@ -867,7 +867,7 @@
"category": "Error",
"code": 2312
},
"Constraint of a type parameter cannot reference any type parameter from the same type parameter list.": {
"Type parameter '{0}' has a circular constraint.": {
"category": "Error",
"code": 2313
},
+1 -1
View File
@@ -2066,7 +2066,6 @@ namespace ts {
resolvedSymbol?: Symbol; // Cached name resolution result
flags?: NodeCheckFlags; // Set of flags specific to Node
enumMemberValue?: number; // Constant value of enum member
isIllegalTypeReferenceInConstraint?: boolean; // Is type reference in constraint refers to the type parameter from the same list
isVisible?: boolean; // Is this node visible
generatedName?: string; // Generated name for module, enum, or import declaration
generatedNames?: Map<string>; // Generated names table for source file
@@ -2312,6 +2311,7 @@ namespace ts {
inferUnionTypes: boolean; // Infer union types for disjoint candidates (otherwise undefinedType)
inferences: TypeInferences[]; // Inferences made for each type parameter
inferredTypes: Type[]; // Inferred type for each type parameter
mapper?: TypeMapper; // Type mapper for this inference context
failedTypeParameterIndex?: number; // Index of type parameter for which inference failed
// It is optional because in contextual signature instantiation, nothing fails
}