Skip to content

Commit 43aafb9

Browse files
author
Andy Hanson
committed
Merge branch 'master' into untyped_module_symbol
2 parents a3feb54 + f673f48 commit 43aafb9

20 files changed

+785
-16
lines changed

src/compiler/binder.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1045,7 +1045,35 @@ namespace ts {
10451045
if (node.finallyBlock) {
10461046
// in finally flow is combined from pre-try/flow from try/flow from catch
10471047
// pre-flow is necessary to make sure that finally is reachable even if finally flows in both try and finally blocks are unreachable
1048-
addAntecedent(preFinallyLabel, preTryFlow);
1048+
1049+
// also for finally blocks we inject two extra edges into the flow graph.
1050+
// first -> edge that connects pre-try flow with the label at the beginning of the finally block, it has lock associated with it
1051+
// second -> edge that represents post-finally flow.
1052+
// these edges are used in following scenario:
1053+
// let a; (1)
1054+
// try { a = someOperation(); (2)}
1055+
// finally { (3) console.log(a) } (4)
1056+
// (5) a
1057+
1058+
// flow graph for this case looks roughly like this (arrows show ):
1059+
// (1-pre-try-flow) <--.. <-- (2-post-try-flow)
1060+
// ^ ^
1061+
// |*****(3-pre-finally-label) -----|
1062+
// ^
1063+
// |-- ... <-- (4-post-finally-label) <--- (5)
1064+
// In case when we walk the flow starting from inside the finally block we want to take edge '*****' into account
1065+
// since it ensures that finally is always reachable. However when we start outside the finally block and go through label (5)
1066+
// then edge '*****' should be discarded because label 4 is only reachable if post-finally label-4 is reachable
1067+
// Simply speaking code inside finally block is treated as reachable as pre-try-flow
1068+
// since we conservatively assume that any line in try block can throw or return in which case we'll enter finally.
1069+
// However code after finally is reachable only if control flow was not abrupted in try/catch or finally blocks - it should be composed from
1070+
// final flows of these blocks without taking pre-try flow into account.
1071+
//
1072+
// extra edges that we inject allows to control this behavior
1073+
// if when walking the flow we step on post-finally edge - we can mark matching pre-finally edge as locked so it will be skipped.
1074+
const preFinallyFlow: PreFinallyFlow = { flags: FlowFlags.PreFinally, antecedent: preTryFlow, lock: {} };
1075+
addAntecedent(preFinallyLabel, preFinallyFlow);
1076+
10491077
currentFlow = finishFlowLabel(preFinallyLabel);
10501078
bind(node.finallyBlock);
10511079
// if flow after finally is unreachable - keep it
@@ -1061,6 +1089,11 @@ namespace ts {
10611089
: unreachableFlow;
10621090
}
10631091
}
1092+
if (!(currentFlow.flags & FlowFlags.Unreachable)) {
1093+
const afterFinallyFlow: AfterFinallyFlow = { flags: FlowFlags.AfterFinally, antecedent: currentFlow };
1094+
preFinallyFlow.lock = afterFinallyFlow;
1095+
currentFlow = afterFinallyFlow;
1096+
}
10641097
}
10651098
else {
10661099
currentFlow = finishFlowLabel(preFinallyLabel);

src/compiler/checker.ts

Lines changed: 77 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2696,7 +2696,11 @@ namespace ts {
26962696
writePunctuation(writer, SyntaxKind.ColonToken);
26972697
writeSpace(writer);
26982698

2699-
buildTypeDisplay(getTypeOfSymbol(p), writer, enclosingDeclaration, flags, symbolStack);
2699+
let type = getTypeOfSymbol(p);
2700+
if (isRequiredInitializedParameter(parameterNode)) {
2701+
type = includeFalsyTypes(type, TypeFlags.Undefined);
2702+
}
2703+
buildTypeDisplay(type, writer, enclosingDeclaration, flags, symbolStack);
27002704
}
27012705

27022706
function buildBindingPatternDisplay(bindingPattern: BindingPattern, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags, symbolStack?: Symbol[]) {
@@ -3277,6 +3281,16 @@ namespace ts {
32773281
return strictNullChecks && optional ? includeFalsyTypes(type, TypeFlags.Undefined) : type;
32783282
}
32793283

3284+
/** remove undefined from the annotated type of a parameter when there is an initializer (that doesn't include undefined) */
3285+
function removeOptionalityFromAnnotation(annotatedType: Type, declaration: VariableLikeDeclaration): Type {
3286+
const annotationIncludesUndefined = strictNullChecks &&
3287+
declaration.kind === SyntaxKind.Parameter &&
3288+
declaration.initializer &&
3289+
getFalsyFlags(annotatedType) & TypeFlags.Undefined &&
3290+
!(getFalsyFlags(checkExpression(declaration.initializer)) & TypeFlags.Undefined);
3291+
return annotationIncludesUndefined ? getNonNullableType(annotatedType) : annotatedType;
3292+
}
3293+
32803294
// Return the inferred type for a variable, parameter, or property declaration
32813295
function getTypeForVariableLikeDeclaration(declaration: VariableLikeDeclaration, includeOptionality: boolean): Type {
32823296
if (declaration.flags & NodeFlags.JavaScriptFile) {
@@ -3310,7 +3324,8 @@ namespace ts {
33103324

33113325
// Use type from type annotation if one is present
33123326
if (declaration.type) {
3313-
return addOptionality(getTypeFromTypeNode(declaration.type), /*optional*/ declaration.questionToken && includeOptionality);
3327+
const declaredType = removeOptionalityFromAnnotation(getTypeFromTypeNode(declaration.type), declaration);
3328+
return addOptionality(declaredType, /*optional*/ declaration.questionToken && includeOptionality);
33143329
}
33153330

33163331
if ((compilerOptions.noImplicitAny || declaration.flags & NodeFlags.JavaScriptFile) &&
@@ -5204,6 +5219,12 @@ namespace ts {
52045219
Debug.assert(parameterIndex >= 0);
52055220
return parameterIndex >= signature.minArgumentCount;
52065221
}
5222+
const iife = getImmediatelyInvokedFunctionExpression(node.parent);
5223+
if (iife) {
5224+
return !node.type &&
5225+
!node.dotDotDotToken &&
5226+
indexOf((node.parent as SignatureDeclaration).parameters, node) >= iife.arguments.length;
5227+
}
52075228

52085229
return false;
52095230
}
@@ -7768,16 +7789,36 @@ namespace ts {
77687789
if (target.flags & TypeFlags.Union && containsType(targetTypes, source)) {
77697790
return Ternary.True;
77707791
}
7771-
const len = targetTypes.length;
7772-
for (let i = 0; i < len; i++) {
7773-
const related = isRelatedTo(source, targetTypes[i], reportErrors && i === len - 1);
7792+
for (const type of targetTypes) {
7793+
const related = isRelatedTo(source, type, /*reportErrors*/ false);
77747794
if (related) {
77757795
return related;
77767796
}
77777797
}
7798+
if (reportErrors) {
7799+
const discriminantType = findMatchingDiscriminantType(source, target);
7800+
isRelatedTo(source, discriminantType || targetTypes[targetTypes.length - 1], /*reportErrors*/ true);
7801+
}
77787802
return Ternary.False;
77797803
}
77807804

7805+
function findMatchingDiscriminantType(source: Type, target: UnionOrIntersectionType) {
7806+
const sourceProperties = getPropertiesOfObjectType(source);
7807+
if (sourceProperties) {
7808+
for (const sourceProperty of sourceProperties) {
7809+
if (isDiscriminantProperty(target, sourceProperty.name)) {
7810+
const sourceType = getTypeOfSymbol(sourceProperty);
7811+
for (const type of target.types) {
7812+
const targetType = getTypeOfPropertyOfType(type, sourceProperty.name);
7813+
if (targetType && isRelatedTo(sourceType, targetType)) {
7814+
return type;
7815+
}
7816+
}
7817+
}
7818+
}
7819+
}
7820+
}
7821+
77817822
function typeRelatedToEachType(source: Type, target: UnionOrIntersectionType, reportErrors: boolean): Ternary {
77827823
let result = Ternary.True;
77837824
const targetTypes = target.types;
@@ -9881,7 +9922,19 @@ namespace ts {
98819922
}
98829923
}
98839924
let type: FlowType;
9884-
if (flow.flags & FlowFlags.Assignment) {
9925+
if (flow.flags & FlowFlags.AfterFinally) {
9926+
// block flow edge: finally -> pre-try (for larger explanation check comment in binder.ts - bindTryStatement
9927+
(<AfterFinallyFlow>flow).locked = true;
9928+
type = getTypeAtFlowNode((<AfterFinallyFlow>flow).antecedent);
9929+
(<AfterFinallyFlow>flow).locked = false;
9930+
}
9931+
else if (flow.flags & FlowFlags.PreFinally) {
9932+
// locked pre-finally flows are filtered out in getTypeAtFlowBranchLabel
9933+
// so here just redirect to antecedent
9934+
flow = (<PreFinallyFlow>flow).antecedent;
9935+
continue;
9936+
}
9937+
else if (flow.flags & FlowFlags.Assignment) {
98859938
type = getTypeAtFlowAssignment(<FlowAssignment>flow);
98869939
if (!type) {
98879940
flow = (<FlowAssignment>flow).antecedent;
@@ -10037,6 +10090,12 @@ namespace ts {
1003710090
let subtypeReduction = false;
1003810091
let seenIncomplete = false;
1003910092
for (const antecedent of flow.antecedents) {
10093+
if (antecedent.flags & FlowFlags.PreFinally && (<PreFinallyFlow>antecedent).lock.locked) {
10094+
// if flow correspond to branch from pre-try to finally and this branch is locked - this means that
10095+
// we initially have started following the flow outside the finally block.
10096+
// in this case we should ignore this branch.
10097+
continue;
10098+
}
1004010099
const flowType = getTypeAtFlowNode(antecedent);
1004110100
const type = getTypeFromFlowType(flowType);
1004210101
// If the type at a particular antecedent path is the declared type and the
@@ -20708,6 +20767,13 @@ namespace ts {
2070820767
return false;
2070920768
}
2071020769

20770+
function isRequiredInitializedParameter(parameter: ParameterDeclaration) {
20771+
return strictNullChecks &&
20772+
!isOptionalParameter(parameter) &&
20773+
parameter.initializer &&
20774+
!(getModifierFlags(parameter) & ModifierFlags.ParameterPropertyModifier);
20775+
}
20776+
2071120777
function getNodeCheckFlags(node: Node): NodeCheckFlags {
2071220778
node = getParseTreeNode(node);
2071320779
return node ? getNodeLinks(node).flags : undefined;
@@ -20799,10 +20865,12 @@ namespace ts {
2079920865
function writeTypeOfDeclaration(declaration: AccessorDeclaration | VariableLikeDeclaration, enclosingDeclaration: Node, flags: TypeFormatFlags, writer: SymbolWriter) {
2080020866
// Get type of the symbol if this is the valid symbol otherwise get type at location
2080120867
const symbol = getSymbolOfNode(declaration);
20802-
const type = symbol && !(symbol.flags & (SymbolFlags.TypeLiteral | SymbolFlags.Signature))
20868+
let type = symbol && !(symbol.flags & (SymbolFlags.TypeLiteral | SymbolFlags.Signature))
2080320869
? getWidenedLiteralType(getTypeOfSymbol(symbol))
2080420870
: unknownType;
20805-
20871+
if (flags & TypeFormatFlags.AddUndefined) {
20872+
type = includeFalsyTypes(type, TypeFlags.Undefined);
20873+
}
2080620874
getSymbolDisplayBuilder().buildTypeDisplay(type, writer, enclosingDeclaration, flags);
2080720875
}
2080820876

@@ -20901,6 +20969,7 @@ namespace ts {
2090120969
isTopLevelValueImportEqualsWithEntityName,
2090220970
isDeclarationVisible,
2090320971
isImplementationOfOverload,
20972+
isRequiredInitializedParameter,
2090420973
writeTypeOfDeclaration,
2090520974
writeReturnTypeOfSignatureDeclaration,
2090620975
writeTypeOfExpression,

src/compiler/declarationEmitter.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -324,13 +324,20 @@ namespace ts {
324324
function writeTypeOfDeclaration(declaration: AccessorDeclaration | VariableLikeDeclaration, type: TypeNode, getSymbolAccessibilityDiagnostic: GetSymbolAccessibilityDiagnostic) {
325325
writer.getSymbolAccessibilityDiagnostic = getSymbolAccessibilityDiagnostic;
326326
write(": ");
327-
if (type) {
327+
328+
// use the checker's type, not the declared type,
329+
// for non-optional initialized parameters that aren't a parameter property
330+
const shouldUseResolverType = declaration.kind === SyntaxKind.Parameter &&
331+
resolver.isRequiredInitializedParameter(declaration as ParameterDeclaration);
332+
if (type && !shouldUseResolverType) {
328333
// Write the type
329334
emitType(type);
330335
}
331336
else {
332337
errorNameNode = declaration.name;
333-
resolver.writeTypeOfDeclaration(declaration, enclosingDeclaration, TypeFormatFlags.UseTypeOfFunction | TypeFormatFlags.UseTypeAliasValue, writer);
338+
const format = TypeFormatFlags.UseTypeOfFunction | TypeFormatFlags.UseTypeAliasValue |
339+
(shouldUseResolverType ? TypeFormatFlags.AddUndefined : 0);
340+
resolver.writeTypeOfDeclaration(declaration, enclosingDeclaration, format, writer);
334341
errorNameNode = undefined;
335342
}
336343
}

src/compiler/types.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2071,10 +2071,25 @@
20712071
ArrayMutation = 1 << 8, // Potential array mutation
20722072
Referenced = 1 << 9, // Referenced as antecedent once
20732073
Shared = 1 << 10, // Referenced as antecedent more than once
2074+
PreFinally = 1 << 11, // Injected edge that links pre-finally label and pre-try flow
2075+
AfterFinally = 1 << 12, // Injected edge that links post-finally flow with the rest of the graph
20742076
Label = BranchLabel | LoopLabel,
20752077
Condition = TrueCondition | FalseCondition
20762078
}
20772079

2080+
export interface FlowLock {
2081+
locked?: boolean;
2082+
}
2083+
2084+
export interface AfterFinallyFlow extends FlowNode, FlowLock {
2085+
antecedent: FlowNode;
2086+
}
2087+
2088+
export interface PreFinallyFlow extends FlowNode {
2089+
antecedent: FlowNode;
2090+
lock: FlowLock;
2091+
}
2092+
20782093
export interface FlowNode {
20792094
flags: FlowFlags;
20802095
id?: number; // Node id used by flow type cache in checker
@@ -2474,7 +2489,8 @@
24742489
InFirstTypeArgument = 0x00000100, // Writing first type argument of the instantiated type
24752490
InTypeAlias = 0x00000200, // Writing type in type alias declaration
24762491
UseTypeAliasValue = 0x00000400, // Serialize the type instead of using type-alias. This is needed when we emit declaration file.
2477-
SuppressAnyReturnType = 0x00000800, // If the return type is any-like, don't offer a return type.
2492+
SuppressAnyReturnType = 0x00000800, // If the return type is any-like, don't offer a return type.
2493+
AddUndefined = 0x00001000, // Add undefined to types of initialized, non-optional parameters
24782494
}
24792495

24802496
export const enum SymbolFormatFlags {
@@ -2579,6 +2595,7 @@
25792595
isDeclarationVisible(node: Declaration): boolean;
25802596
collectLinkedAliases(node: Identifier): Node[];
25812597
isImplementationOfOverload(node: FunctionLikeDeclaration): boolean;
2598+
isRequiredInitializedParameter(node: ParameterDeclaration): boolean;
25822599
writeTypeOfDeclaration(declaration: AccessorDeclaration | VariableLikeDeclaration, enclosingDeclaration: Node, flags: TypeFormatFlags, writer: SymbolWriter): void;
25832600
writeReturnTypeOfSignatureDeclaration(signatureDeclaration: SignatureDeclaration, enclosingDeclaration: Node, flags: TypeFormatFlags, writer: SymbolWriter): void;
25842601
writeTypeOfExpression(expr: Expression, enclosingDeclaration: Node, flags: TypeFormatFlags, writer: SymbolWriter): void;

tests/baselines/reference/contextuallyTypedIife.types

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -253,17 +253,17 @@ let eleven = (o => o.a(11))({ a: function(n) { return n; } });
253253
// missing arguments
254254
(function(x, undefined) { return x; })(42);
255255
>(function(x, undefined) { return x; })(42) : number
256-
>(function(x, undefined) { return x; }) : (x: number, undefined: any) => number
257-
>function(x, undefined) { return x; } : (x: number, undefined: any) => number
256+
>(function(x, undefined) { return x; }) : (x: number, undefined?: any) => number
257+
>function(x, undefined) { return x; } : (x: number, undefined?: any) => number
258258
>x : number
259259
>undefined : any
260260
>x : number
261261
>42 : 42
262262

263263
((x, y, z) => 42)();
264264
>((x, y, z) => 42)() : number
265-
>((x, y, z) => 42) : (x: any, y: any, z: any) => number
266-
>(x, y, z) => 42 : (x: any, y: any, z: any) => number
265+
>((x, y, z) => 42) : (x?: any, y?: any, z?: any) => number
266+
>(x, y, z) => 42 : (x?: any, y?: any, z?: any) => number
267267
>x : any
268268
>y : any
269269
>z : any

0 commit comments

Comments
 (0)