Skip to content

Commit e29ae15

Browse files
committed
Partial Types (microsoft#11233)
1 parent 137c99b commit e29ae15

24 files changed

+812
-13
lines changed

src/compiler/checker.ts

Lines changed: 131 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1574,6 +1574,10 @@ namespace ts {
15741574
}
15751575

15761576
function symbolIsValue(symbol: Symbol): boolean {
1577+
if (symbol.partialSource) {
1578+
return symbolIsValue(symbol.partialSource);
1579+
}
1580+
15771581
// If it is an instantiated symbol, then it is a value if the symbol it is an
15781582
// instantiation of is a value.
15791583
if (symbol.flags & SymbolFlags.Instantiated) {
@@ -1629,6 +1633,12 @@ namespace ts {
16291633
return type;
16301634
}
16311635

1636+
function createPartialTypeFromObjectType(target: Type): Type {
1637+
const type = <PartialType>createType(TypeFlags.Partial | TypeFlags.ObjectType);
1638+
type.type = target;
1639+
return type;
1640+
}
1641+
16321642
// A reserved member name starts with two underscores, but the third character cannot be an underscore
16331643
// or the @ symbol. A third underscore indicates an escaped form of an identifer that started
16341644
// with at least two underscores. The @ character indicates that the name is denoted by a well known ES
@@ -2216,6 +2226,10 @@ namespace ts {
22162226
}
22172227
writer.writeKeyword("this");
22182228
}
2229+
else if (type.flags & TypeFlags.Partial) {
2230+
writer.writeKeyword("partial ");
2231+
writeType((type as PartialType).type, flags);
2232+
}
22192233
else if (getObjectFlags(type) & ObjectFlags.Reference) {
22202234
writeTypeReference(<TypeReference>type, nextFlags);
22212235
}
@@ -2787,6 +2801,7 @@ namespace ts {
27872801
case SyntaxKind.UnionType:
27882802
case SyntaxKind.IntersectionType:
27892803
case SyntaxKind.ParenthesizedType:
2804+
case SyntaxKind.PartialType:
27902805
return isDeclarationVisible(<Declaration>node.parent);
27912806

27922807
// Default binding, import specifier and namespace import is visible
@@ -3530,7 +3545,24 @@ namespace ts {
35303545
return links.type;
35313546
}
35323547

3548+
function getTypeOfPartialPropertySymbol(symbol: Symbol): Type {
3549+
const links = getSymbolLinks(symbol);
3550+
if (!links.type) {
3551+
const type = getTypeOfSymbol(symbol.partialSource);
3552+
if (strictNullChecks) {
3553+
links.type = getUnionType([type, undefinedType]);
3554+
}
3555+
else {
3556+
links.type = type;
3557+
}
3558+
}
3559+
return links.type;
3560+
}
3561+
35333562
function getTypeOfSymbol(symbol: Symbol): Type {
3563+
if (symbol.partialSource) {
3564+
return getTypeOfPartialPropertySymbol(symbol);
3565+
}
35343566
if (symbol.flags & SymbolFlags.Instantiated) {
35353567
return getTypeOfInstantiatedSymbol(symbol);
35363568
}
@@ -4187,6 +4219,29 @@ namespace ts {
41874219
resolveObjectTypeMembers(type, source, typeParameters, typeArguments);
41884220
}
41894221

4222+
function resolvePartialTypeMembers(type: PartialType) {
4223+
const source = getPropertiesOfType(type.type);
4224+
const members = createMap<Symbol>();
4225+
for (const member of source) {
4226+
if (member.flags & SymbolFlags.Optional) {
4227+
members[member.name] = member;
4228+
}
4229+
else {
4230+
const synthetic = createSymbol(member.flags | SymbolFlags.SyntheticProperty | SymbolFlags.Optional, member.name);
4231+
getSymbolLinks(synthetic).originalType = type.type;
4232+
synthetic.partialSource = member;
4233+
members[member.name] = synthetic;
4234+
}
4235+
}
4236+
const stringIndex = getIndexInfoOfType(type.type, IndexKind.String);
4237+
const numberIndex = getIndexInfoOfType(type.type, IndexKind.Number);
4238+
const stringIndexType = stringIndex && (strictNullChecks ? getUnionType([stringIndex.type, undefinedType]) : stringIndex.type);
4239+
const numberIndexType = numberIndex && (strictNullChecks ? getUnionType([numberIndex.type, undefinedType]) : numberIndex.type);
4240+
setObjectTypeMembers(type, members, emptyArray, emptyArray,
4241+
stringIndex && createIndexInfo(stringIndexType, stringIndex.isReadonly, stringIndex.declaration),
4242+
numberIndex && createIndexInfo(numberIndexType, numberIndex.isReadonly, numberIndex.declaration));
4243+
}
4244+
41904245
function createSignature(declaration: SignatureDeclaration, typeParameters: TypeParameter[], thisParameter: Symbol | undefined, parameters: Symbol[],
41914246
resolvedReturnType: Type, typePredicate: TypePredicate, minArgumentCount: number, hasRestParameter: boolean, hasLiteralTypes: boolean): Signature {
41924247
const sig = new Signature(checker);
@@ -4407,6 +4462,8 @@ namespace ts {
44074462
}
44084463
else if ((<ObjectType>type).objectFlags & ObjectFlags.Anonymous) {
44094464
resolveAnonymousTypeMembers(<AnonymousType>type);
4465+
} else if ((<ObjectType>type).objectFlags & ObjectFlags.Partial) {
4466+
resolvePartialTypeMembers(<PartialType>type);
44104467
}
44114468
}
44124469
else if (type.flags & TypeFlags.Union) {
@@ -5692,6 +5749,40 @@ namespace ts {
56925749
return links.resolvedType;
56935750
}
56945751

5752+
function getTypeFromPartialTypeNode(node: PartialTypeNode): Type {
5753+
const links = getNodeLinks(node);
5754+
if (!links.resolvedType) {
5755+
links.resolvedType = getPartialType(getTypeOfNode(node.type));
5756+
}
5757+
return links.resolvedType;
5758+
}
5759+
5760+
function getPartialType(type: Type): Type {
5761+
if (type.resolvedPartialType) {
5762+
return type.resolvedPartialType;
5763+
}
5764+
5765+
if (type.flags & TypeFlags.Partial) {
5766+
// partial partial T === partial T
5767+
return type;
5768+
}
5769+
if (type.flags & TypeFlags.Union) {
5770+
return type.resolvedPartialType = getUnionType((type as UnionType).types.map(getPartialType));
5771+
}
5772+
if (type.flags & TypeFlags.Intersection) {
5773+
return type.resolvedPartialType = getIntersectionType((type as IntersectionType).types.map(getPartialType));
5774+
}
5775+
if (type.flags & TypeFlags.ObjectType) {
5776+
return type.resolvedPartialType = createPartialTypeFromObjectType(type);
5777+
}
5778+
5779+
// Type parameter
5780+
Debug.assert(!!(type.flags & TypeFlags.TypeParameter));
5781+
const result = <PartialType>createType(TypeFlags.Partial);
5782+
result.type = type;
5783+
return type.resolvedPartialType = result;
5784+
}
5785+
56955786
function getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node: Node, aliasSymbol?: Symbol, aliasTypeArguments?: Type[]): Type {
56965787
const links = getNodeLinks(node);
56975788
if (!links.resolvedType) {
@@ -5841,6 +5932,8 @@ namespace ts {
58415932
return getTypeFromUnionTypeNode(<UnionTypeNode>node, aliasSymbol, aliasTypeArguments);
58425933
case SyntaxKind.IntersectionType:
58435934
return getTypeFromIntersectionTypeNode(<IntersectionTypeNode>node, aliasSymbol, aliasTypeArguments);
5935+
case SyntaxKind.PartialType:
5936+
return getTypeFromPartialTypeNode(<PartialTypeNode>node);
58445937
case SyntaxKind.ParenthesizedType:
58455938
case SyntaxKind.JSDocNullableType:
58465939
case SyntaxKind.JSDocNonNullableType:
@@ -6120,6 +6213,9 @@ namespace ts {
61206213
if (type.flags & TypeFlags.Intersection) {
61216214
return getIntersectionType(instantiateList((<IntersectionType>type).types, mapper, instantiateType), type.aliasSymbol, mapper.targetTypes);
61226215
}
6216+
if (type.flags & TypeFlags.Partial) {
6217+
return getPartialType(instantiateType((<PartialType>type).type, mapper));
6218+
}
61236219
}
61246220
return type;
61256221
}
@@ -6603,6 +6699,17 @@ namespace ts {
66036699

66046700
if (isSimpleTypeRelatedTo(source, target, relation, reportErrors ? reportError : undefined)) return Ternary.True;
66056701

6702+
// If the target is a 'partial T', the only allowed source is T, partial T, or {}
6703+
if ((target.flags & (TypeFlags.Partial | TypeFlags.ObjectType)) === TypeFlags.Partial) {
6704+
if ((source === emptyObjectType) || ((<PartialType>target).type === source)) {
6705+
return Ternary.True;
6706+
}
6707+
if (reportErrors) {
6708+
reportRelationError(headMessage, source, target);
6709+
}
6710+
return Ternary.False;
6711+
}
6712+
66066713
if (getObjectFlags(source) & ObjectFlags.ObjectLiteral && source.flags & TypeFlags.FreshLiteral) {
66076714
if (hasExcessProperties(<FreshObjectLiteralType>source, target, reportErrors)) {
66086715
if (reportErrors) {
@@ -15021,6 +15128,10 @@ namespace ts {
1502115128
forEach(node.types, checkSourceElement);
1502215129
}
1502315130

15131+
function checkPartialType(node: PartialTypeNode) {
15132+
checkSourceElement(node.type);
15133+
}
15134+
1502415135
function isPrivateWithinAmbient(node: Node): boolean {
1502515136
return (getModifierFlags(node) & ModifierFlags.Private) && isInAmbientContext(node);
1502615137
}
@@ -18287,6 +18398,8 @@ namespace ts {
1828718398
case SyntaxKind.UnionType:
1828818399
case SyntaxKind.IntersectionType:
1828918400
return checkUnionOrIntersectionType(<UnionOrIntersectionTypeNode>node);
18401+
case SyntaxKind.PartialType:
18402+
return checkPartialType(<PartialTypeNode>node);
1829018403
case SyntaxKind.ParenthesizedType:
1829118404
return checkSourceElement((<ParenthesizedTypeNode>node).type);
1829218405
case SyntaxKind.FunctionDeclaration:
@@ -18639,7 +18752,7 @@ namespace ts {
1863918752
node = node.parent;
1864018753
}
1864118754

18642-
return node.parent && (node.parent.kind === SyntaxKind.TypeReference || node.parent.kind === SyntaxKind.JSDocTypeReference) ;
18755+
return node.parent && (node.parent.kind === SyntaxKind.TypeReference || node.parent.kind === SyntaxKind.JSDocTypeReference);
1864318756
}
1864418757

1864518758
function isHeritageClauseElementIdentifier(entityName: Node): boolean {
@@ -19031,15 +19144,23 @@ namespace ts {
1903119144

1903219145
function getRootSymbols(symbol: Symbol): Symbol[] {
1903319146
if (symbol.flags & SymbolFlags.SyntheticProperty) {
19034-
const symbols: Symbol[] = [];
19035-
const name = symbol.name;
19036-
forEach(getSymbolLinks(symbol).containingType.types, t => {
19037-
const symbol = getPropertyOfType(t, name);
19038-
if (symbol) {
19039-
symbols.push(symbol);
19040-
}
19041-
});
19042-
return symbols;
19147+
const links = getSymbolLinks(symbol);
19148+
if (links.containingType) {
19149+
const symbols: Symbol[] = [];
19150+
const name = symbol.name;
19151+
19152+
forEach(links.containingType.types, t => {
19153+
const symbol = getPropertyOfType(t, name);
19154+
if (symbol) {
19155+
symbols.push(symbol);
19156+
}
19157+
});
19158+
return symbols;
19159+
}
19160+
else if (links.originalType) {
19161+
return [links.originalType.symbol];
19162+
}
19163+
return emptyArray;
1904319164
}
1904419165
else if (symbol.flags & SymbolFlags.Transient) {
1904519166
let target: Symbol;

src/compiler/parser.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,8 @@ namespace ts {
133133
case SyntaxKind.UnionType:
134134
case SyntaxKind.IntersectionType:
135135
return visitNodes(cbNodes, (<UnionOrIntersectionTypeNode>node).types);
136+
case SyntaxKind.PartialType:
137+
return visitNode(cbNode, (<PartialTypeNode>node).type);
136138
case SyntaxKind.ParenthesizedType:
137139
return visitNode(cbNode, (<ParenthesizedTypeNode>node).type);
138140
case SyntaxKind.LiteralType:
@@ -422,10 +424,11 @@ namespace ts {
422424
case SyntaxKind.PartiallyEmittedExpression:
423425
return visitNode(cbNode, (<PartiallyEmittedExpression>node).expression);
424426
case SyntaxKind.JSDocLiteralType:
425-
return visitNode(cbNode, (<JSDocLiteralType>node).literal);
427+
return visitNode(cbNode, (<JSDocLiteralType>node).literal);
426428
}
427429
}
428430

431+
429432
export function createSourceFile(fileName: string, sourceText: string, languageVersion: ScriptTarget, setParentNodes = false, scriptKind?: ScriptKind): SourceFile {
430433
performance.mark("beforeParse");
431434
const result = Parser.parseSourceFile(fileName, sourceText, languageVersion, /*syntaxCursor*/ undefined, setParentNodes, scriptKind);
@@ -2038,6 +2041,13 @@ namespace ts {
20382041
return finishNode(node);
20392042
}
20402043

2044+
function parsePartialType(): PartialTypeNode {
2045+
const node = <PartialTypeNode>createNode(SyntaxKind.PartialType);
2046+
parseExpected(SyntaxKind.PartialKeyword);
2047+
node.type = parseType();
2048+
return finishNode(node);
2049+
}
2050+
20412051
function parseTypeParameter(): TypeParameterDeclaration {
20422052
const node = <TypeParameterDeclaration>createNode(SyntaxKind.TypeParameter);
20432053
node.name = parseIdentifier();
@@ -2473,6 +2483,9 @@ namespace ts {
24732483
return parseTupleType();
24742484
case SyntaxKind.OpenParenToken:
24752485
return parseParenthesizedType();
2486+
case SyntaxKind.PartialKeyword:
2487+
const partialNode = tryParse(parsePartialType);
2488+
return partialNode || parseTypeReference();
24762489
default:
24772490
return parseTypeReference();
24782491
}

src/compiler/scanner.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ namespace ts {
9898
"null": SyntaxKind.NullKeyword,
9999
"number": SyntaxKind.NumberKeyword,
100100
"package": SyntaxKind.PackageKeyword,
101+
"partial": SyntaxKind.PartialKeyword,
101102
"private": SyntaxKind.PrivateKeyword,
102103
"protected": SyntaxKind.ProtectedKeyword,
103104
"public": SyntaxKind.PublicKeyword,

src/compiler/types.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,8 @@ namespace ts {
181181
UndefinedKeyword,
182182
FromKeyword,
183183
GlobalKeyword,
184-
OfKeyword, // LastKeyword and LastToken
184+
OfKeyword,
185+
PartialKeyword,
185186

186187
// Parse tree nodes
187188

@@ -214,6 +215,7 @@ namespace ts {
214215
TupleType,
215216
UnionType,
216217
IntersectionType,
218+
PartialType,
217219
ParenthesizedType,
218220
ThisType,
219221
LiteralType,
@@ -882,6 +884,12 @@ namespace ts {
882884
type: TypeNode;
883885
}
884886

887+
// @kind(SyntaxKind.PartialType)
888+
export interface PartialTypeNode extends TypeNode {
889+
type: TypeNode;
890+
}
891+
892+
// @kind(SyntaxKind.StringLiteralType)
885893
export interface LiteralTypeNode extends TypeNode {
886894
kind: SyntaxKind.LiteralType;
887895
literal: Expression;
@@ -2565,6 +2573,7 @@ namespace ts {
25652573
/* @internal */ isReferenced?: boolean; // True if the symbol is referenced elsewhere
25662574
/* @internal */ isReplaceableByMethod?: boolean; // Can this Javascript class property be replaced by a method symbol?
25672575
/* @internal */ isAssigned?: boolean; // True if the symbol is a parameter with assignments
2576+
/* @internal */ partialSource?: Symbol; // True if the symbol is a synthetic partial property
25682577
}
25692578

25702579
/* @internal */
@@ -2578,6 +2587,7 @@ namespace ts {
25782587
mapper?: TypeMapper; // Type mapper for instantiation alias
25792588
referenced?: boolean; // True if alias symbol has been referenced as a value
25802589
containingType?: UnionOrIntersectionType; // Containing union or intersection type for synthetic property
2590+
originalType?: Type; // Originating type for a partial type synthetic property
25812591
hasNonUniformType?: boolean; // True if constituents have non-uniform types
25822592
isPartial?: boolean; // True if syntheric property of union type occurs in some but not all constituents
25832593
isDiscriminantProperty?: boolean; // True if discriminant synthetic property
@@ -2675,6 +2685,7 @@ namespace ts {
26752685
ContainsObjectLiteral = 1 << 20, // Type is or contains object literal type
26762686
/* @internal */
26772687
ContainsAnyFunctionType = 1 << 21, // Type is or contains object literal type
2688+
Partial = 1 << 22, // Partial type
26782689

26792690
/* @internal */
26802691
Nullable = Undefined | Null,
@@ -2715,6 +2726,7 @@ namespace ts {
27152726
pattern?: DestructuringPattern; // Destructuring pattern represented by type (if any)
27162727
aliasSymbol?: Symbol; // Alias associated with type
27172728
aliasTypeArguments?: Type[]; // Alias type arguments (if any)
2729+
resolvedPartialType?: Type; // If we create a partial type for this, it's cached here
27182730
}
27192731

27202732
/* @internal */
@@ -2759,6 +2771,11 @@ namespace ts {
27592771
objectFlags: ObjectFlags;
27602772
}
27612773

2774+
// Partial types
2775+
export interface PartialType extends Type {
2776+
type: Type;
2777+
}
2778+
27622779
// Class and interface types (TypeFlags.Class and TypeFlags.Interface)
27632780
export interface InterfaceType extends ObjectType {
27642781
typeParameters: TypeParameter[]; // Type parameters (undefined if non-generic)

src/services/symbolDisplay.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ namespace ts.SymbolDisplay {
5858
if (rootSymbolFlags & (SymbolFlags.PropertyOrAccessor | SymbolFlags.Variable)) {
5959
return ScriptElementKind.memberVariableElement;
6060
}
61-
Debug.assert(!!(rootSymbolFlags & SymbolFlags.Method));
6261
});
6362
if (!unionPropertyKind) {
6463
// If this was union of all methods,

0 commit comments

Comments
 (0)