8000 Strict function types by ahejlsberg · Pull Request #18654 · microsoft/TypeScript · GitHub
[go: up one dir, main page]

Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Strict function types #18654

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 25 commits into from
Oct 2, 2017
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
12f5dd8
Introduce --strictFunctionTypes mode
ahejlsberg Sep 18, 2017
f8ff7f7
Use dedicated marker types for variance determination
ahejlsberg Sep 18, 2017
670d711
Add quick path for computing array variance as it is already known
ahejlsberg Sep 18, 2017
a0fa69f
Handle contravariance in type inference
ahejlsberg Sep 19, 2017
b58e0fb
Add comments
ahejlsberg Sep 19, 2017
84f7afd
Handle special case of 'void' type arguments for covariant type param…
ahejlsberg Sep 19, 2017
54eadef
Accept new baselines
ahejlsberg Sep 19, 2017
44cc8c5
Use methods in dom.generated.d.ts to opt out of strict checks
ahejlsberg Sep 19, 2017
dd466ae
Update tsconfig baselines
ahejlsberg Sep 19, 2017
24698dd
Revert dom.generated.d.ts and fix duplicate declarations
ahejlsberg Sep 20, 2017
f8e2cc1
Properly flag and structurally compare marker type references
ahejlsberg Sep 21, 2017
589e1f4
Update comment
ahejlsberg Sep 21, 2017
afc8a26
Always perform structural comparison when variance check fails
ahejlsberg Sep 22, 2017
70e8f73
Add tests
ahejlsberg Sep 22, 2017
91691f6
Strict function type checking only for certain function types
ahejlsberg Sep 25, 2017
6a481e8
Update tests
ahejlsberg Sep 25, 2017
1795614
Accept new baselines
ahejlsberg Sep 26, 2017
5613be4
Only methods and constructors are bivariant in --strictFunctionTypes …
ahejlsberg Sep 28, 2017
1609196
Accept new baselines
ahejlsberg Sep 28, 2017
0756aa1
Merge branch 'master' into strictFunctionTypes
ahejlsberg Sep 28, 2017
c626d9d
Accept new baselines
ahejlsberg Sep 28, 2017
936f98d
Addressing CR feedback
ahejlsberg Sep 29, 2017
bf75a3f
Emit .d.ts file in test
ahejlsberg Oct 2, 2017
bff843a
Improve error elaboration for invariant generic types
ahejlsberg Oct 2, 2017
c2344e0
Add error elaboration test
ahejlsberg Oct 2, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Strict function type checking only for certain function types
  • Loading branch information
ahejlsberg committed Sep 25, 2017
commit 91691f6079edaf77dce77df5e0c0d1176bc0a7b8
120 changes: 89 additions & 31 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,7 @@ namespace ts {
const markerSuperType = <TypeParameter>createType(TypeFlags.TypeParameter);
const markerSubType = <TypeParameter>createType(TypeFlags.TypeParameter);
markerSubType.constraint = markerSuperType;
const markerOtherType = <TypeParameter>createType(TypeFlags.TypeParameter);

const anySignature = createSignature(undefined, undefined, undefined, emptyArray, anyType, /*typePredicate*/ undefined, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false);
const unknownSignature = createSignature(undefined, undefined, undefined, emptyArray, unknownType, /*typePredicate*/ undefined, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false);
Expand Down Expand Up @@ -3548,7 +3549,7 @@ namespace ts {
return;
}

if (resolved.callSignatures.length === 1 && !resolved.constructSignatures.length) {
if (resolved.callSignatures.length === 1 && !resolved.constructSignatures.length && isStrictSignature(resolved.callSignatures[0])) {
const parenthesizeSignature = shouldAddParenthesisAroundFunctionType(resolved.callSignatures[0], flags);
if (parenthesizeSignature) {
writePunctuation(writer, SyntaxKind.OpenParenToken);
Expand All @@ -3559,7 +3560,7 @@ namespace ts {
}
return;
}
if (resolved.constructSignatures.length === 1 && !resolved.callSignatures.length) {
if (resolved.constructSignatures.length === 1 && !resolved.callSignatures.length && isStrictSignature(resolved.constructSignatures[0])) {
if (flags & TypeFormatFlags.InElementType) {
writePunctuation(writer, SyntaxKind.OpenParenToken);
}
Expand Down 8000 Expand Up @@ -8521,6 +8522,17 @@ namespace ts {
/*errorReporter*/ undefined, compareTypesAssignable) !== Ternary.False;
}

// A signature is considered strict if it is declared in a function type literal, a constructor type
// literal, a function expression, an arrow function, or a function declaration with no overloads. A
// strict signature is subject to strict checking in strictFunctionTypes mode.
function isStrictSignature(signature: Signature) {
const declaration = signature.declaration;
const kind = declaration ? declaration.kind : SyntaxKind.Unknown;
return kind === SyntaxKind.FunctionType || kind === SyntaxKind.ConstructorType ||
kind === SyntaxKind.FunctionExpression || kind === SyntaxKind.ArrowFunction ||
(kind === SyntaxKind.FunctionDeclaration && getSingleCallSignature(getTypeOfSymbol(getSymbolOfNode(declaration))));
}

type ErrorReporter = (message: DiagnosticMessage, arg0?: string, arg1?: string) => void;

/**
Expand All @@ -8546,9 +8558,7 @@ namespace ts {
source = instantiateSignatureInContextOf(source, target, /*contextualMapper*/ undefined, compareTypes);
}

const targetKind = target.declaration ? target.declaration.kind : SyntaxKind.Unknown;
const strictVariance = strictFunctionTypes && targetKind !== SyntaxKind.MethodDeclaration && targetKind !== SyntaxKind.MethodSignature;

const strictVariance = strictFunctionTypes && isStrictSignature(target);
let result = Ternary.True;

const sourceThisType = getThisTypeOfSignature(source);
Expand Down Expand Up @@ -9215,15 +9225,41 @@ namespace ts {
// in the process of computing variance information for recursive types and when
// comparing 'this' type arguments.
const variance = i < variances.length ? variances[i] : Variance.Covariant;
const s = sources[i];
const t = targets[i];
const related = variance === Variance.Covariant ? isRelatedTo(s, t, reportErrors) :
variance === Variance.Contravariant ? isRelatedTo(t, s, reportErrors) :
Ternary.False;
if (!related) {
return Ternary.False;
// We ignore arguments for independent type parameters (because they're never witnessed).
if (variance !== Variance.Independent) {
const s = sources[i];
const t = targets[i];
let related = Ternary.True;
if (variance === Variance.Covariant) {
related = isRelatedTo(s, t, reportErrors);
}
else if (variance === Variance.Contravariant) {
related = isRelatedTo(t, s, reportErrors);
}
else if (variance === Variance.Bivariant) {
// In the bivariant case we first compare contravariantly without reporting
// errors. Then, if that doesn't succeed, we compare covariantly with error
// reporting. Thus, error elaboration will be based on the the covariant check,
// which is generally easier to reason about.
related = isRelatedTo(t, s, /*reportErrors*/ false);
if (!related) {
related = isRelatedTo(s, t, reportErrors);
}
}
else {
// In the invariant case we first compare covariantly, and only when that
// succeeds do we proceed to compare contravariantly. Thus, error elaboration
// will typically be based on the covariant check.
related = isRelatedTo(s, t, reportErrors);
if (related) {
related &= isRelatedTo(t, s, reportErrors);
}
}
if (!related) {
return Ternary.False;
}
result &= related;
}
result &= related;
}
return result;
}
Expand Down Expand Up @@ -9388,14 +9424,21 @@ namespace ts {
!(source.flags & TypeFlags.MarkerType || target.flags & TypeFlags.MarkerType)) {
// We have type references to the same generic type, and the type references are not marker
// type references (which are intended by be compared structurally). Obtain the variance
// information for the type parameters and relate the type arguments accordingly. If we do
// not succeed, fall through and do a structural comparison instead (there are instances
// where the variance information isn't accurate, e.g. when type parameters are used only
// in bivariant positions or when a type argument is 'any' or 'void'.)
// information for the type parameters and relate the type arguments accordingly.
const variances = getVariances((<TypeReference>source).target);
if (result = typeArgumentsRelatedTo(<TypeReference>source, <TypeReference>target, variances, reportErrors)) {
return result;
}
// The type arguments did not relate appropriately, but it may be because we have no variance
// information (in which case typeArgumentsRelatedTo defaulted to covariance for all type
// arguments). It might also be the case that the target type has a 'void' type argument for
// a covariant type parameter that is only used in return positions within the generic type
// (in which case any type argument is permitted on the source side). In those cases we proceed
// with a structural comparison. Otherwise, we know for certain the instantiations aren't
// related and we can return here.
if (variances !== emptyArray && !hasCovariantVoidArgument(<TypeReference>target, variances)) {
return Ternary.False;
}
}
// Even if relationship doesn't hold for unions, intersections, or generic type references,
// it may hold in a structural comparison.
Expand Down Expand Up @@ -9814,14 +9857,12 @@ namespace ts {
return result;
}

// Return an array containing the variance of each type parameter. The variance information is
// computed by comparing instantiations of the generic type for type arguments with known relations.
// A type parameter is marked as covariant if a covariant comparison succeeds; otherwise, it is
// marked contravariant if a contravarint comparison succeeds; otherwise, it is marked invariant.
// One form of variance doesn't exclude another, so this information simply serves to indicate
// a "primary" relationship that can be checked as an optimization ahead of a full structural
// comparison. The function returns the emptyArray singleton if we're not in strictFunctionTypes
// mode or if the function has been invoked recursively for the given generic type.
// Return an array containing the variance of each type parameter. The variance is effectively
// a digest of the type comparisons that occur for each type argument when instantiations of the
// generic type are structurally compared. We infer the variance information by comparing
// instantiations of the generic type for type arguments with known relations. The function
// returns the emptyArray singleton if we're not in strictFunctionTypes mode or if the function
// has been invoked recursively for the given generic type.
function getVariances(type: GenericType): Variance[] {
if (!strictFunctionTypes) {
return emptyArray;
Expand All @@ -9838,14 +9879,20 @@ namespace ts {
type.variances = emptyArray;
variances = [];
for (const tp of typeParameters) {
// We compare instantiations where the type parameter is replaced with marker types
// that have a known subtype relationship. From this we infer covariance, contravariance
// or invariance.
// We first compare instantiations where the type parameter is replaced with
// marker types that have a known subtype relationship. From this we can infer
// invariance, covariance, contravariance or bivariance.
const typeWithSuper = getMarkerTypeReference( 8000 type, tp, markerSuperType);
const typeWithSub = getMarkerTypeReference(type, tp, markerSubType);
const variance = isTypeAssignableTo(typeWithSub, typeWithSuper) ? Variance.Covariant :
isTypeAssignableTo(typeWithSuper, typeWithSub) ? Variance.Contravariant :
Variance.Invariant;
let variance = (isTypeAssignableTo(typeWithSub, typeWithSuper) ? Variance.Covariant : 0) |
(isTypeAssignableTo(typeWithSuper, typeWithSub) ? Variance.Contravariant : 0);
// If the instantiations appear to be related bivariantly it may be because the
// type parameter is independent (i.e. it isn't witnessed anywhere in the generic
// type). To determine this we compare instantiations where the type parameter is
// replaced with marker types that are known to be unrelated.
if (variance === Variance.Bivariant && isTypeAssignableTo(getMarkerTypeReference(type, tp, markerOtherType), typeWithSuper)) {
variance = Variance.Independent;
}
variances.push(variance);
}
}
Expand All @@ -9854,6 +9901,17 @@ namespace ts {
return variances;
}

// Return true if the given type reference has a 'void' type argument for a covariant type parameter.
// See comment at call in recursiveTypeRelatedTo for when this case matters.
function hasCovariantVoidArgument(type: TypeReference, variances: Variance[]): boolean {
for (let i = 0; i < variances.length; i++) {
if (variances[i] === Variance.Covariant && type.typeArguments[i].flags & TypeFlags.Void) {
return true;
}
}
return false;
}

function isUnconstrainedTypeParameter(type: Type) {
return type.flags & TypeFlags.TypeParameter && !getConstraintFromTypeParameter(<TypeParameter>type);
}
Expand Down
2 changes: 2 additions & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3348,6 +3348,8 @@ namespace ts {
Invariant = 0, // Neither covariant nor contravariant
Covariant = 1, // Covariant
Contravariant = 2, // Contravariant
Bivariant = 3, // Both covariant and contravariant
Independent = 4, // Unwitnessed type parameter
}

// Generic class and interface types
Expand Down
0