Skip to content
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

feat(47595): allow using private fields in type queries #61343

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
40 changes: 39 additions & 1 deletion src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -945,6 +945,8 @@ import {
PredicateSemantics,
PrefixUnaryExpression,
PrivateIdentifier,
PrivateNameType,
PrivateNameTypeNode,
Program,
PromiseOrAwaitableType,
PropertyAccessChain,
Expand Down Expand Up @@ -2042,6 +2044,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
var evolvingArrayTypes: EvolvingArrayType[] = [];
var undefinedProperties: SymbolTable = new Map();
var markerTypes = new Set<number>();
var privateNameTypes = new Map<string, PrivateNameType>();

var unknownSymbol = createSymbol(SymbolFlags.Property, "unknown" as __String);
var resolvingSymbol = createSymbol(0, InternalSymbolName.Resolving);
Expand Down Expand Up @@ -6496,6 +6499,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
return symbolToTypeNode(type.symbol, context, SymbolFlags.Type);
}
if (type.flags & TypeFlags.PrivateNameType) {
context.approximateLength += (type as PrivateNameType).value.length;
return factory.createPrivateNameTypeNode(factory.createPrivateIdentifier((type as PrivateNameType).value));
}
if (type.flags & TypeFlags.StringLiteral) {
context.approximateLength += (type as StringLiteralType).value.length + 2;
return factory.createLiteralTypeNode(setEmitFlags(factory.createStringLiteral((type as StringLiteralType).value, !!(context.flags & NodeBuilderFlags.UseSingleQuotesForStringLiteralType)), EmitFlags.NoAsciiEscaping));
Expand Down Expand Up @@ -18277,7 +18284,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {

function getLiteralTypeFromPropertyName(name: PropertyName | JsxAttributeName) {
if (isPrivateIdentifier(name)) {
return neverType;
return getPrivateNameType(name);
}
if (isNumericLiteral(name)) {
return getRegularTypeOfLiteralType(checkExpression(name));
Expand Down Expand Up @@ -19397,6 +19404,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return links.resolvedType;
}

function getTypeFromPrivateNameTypeNode(node: PrivateNameTypeNode) {
const links = getNodeLinks(node);
if (!links.resolvedType) {
links.resolvedType = getPrivateNameType(node.name);
}
return links.resolvedType;
}

function resolveImportSymbolType(node: ImportTypeNode, links: NodeLinks, symbol: Symbol, meaning: SymbolFlags) {
const resolvedSymbol = resolveSymbol(symbol);
links.resolvedSymbol = resolvedSymbol;
Expand Down Expand Up @@ -19705,6 +19720,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return esSymbolType;
}

function getPrivateNameType(node: PrivateIdentifier) {
const name = unescapeLeadingUnderscores(node.escapedText);
const symbol = privateNameTypes.get(name) ? undefined : lookupSymbolForPrivateIdentifierDeclaration(node.escapedText, node);
if (
symbol?.valueDeclaration &&
isPrivateIdentifierClassElementDeclaration(symbol.valueDeclaration)
) {
privateNameTypes.set(name, createLiteralType(TypeFlags.StringLiteral | TypeFlags.PrivateNameType, name, symbol) as PrivateNameType);
}
return privateNameTypes.get(name) ?? neverType;
}

function getThisType(node: Node): Type {
const container = getThisContainer(node, /*includeArrowFunctions*/ false, /*includeClassComputedPropertyName*/ false);
const parent = container && container.parent;
Expand Down Expand Up @@ -19872,6 +19899,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
case SyntaxKind.PropertyAccessExpression as TypeNodeSyntaxKind:
const symbol = getSymbolAtLocation(node);
return symbol ? getDeclaredTypeOfSymbol(symbol) : errorType;
case SyntaxKind.PrivateNameType:
return getTypeFromPrivateNameTypeNode(node as PrivateNameTypeNode);
default:
return errorType;
}
Expand Down Expand Up @@ -42187,6 +42216,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
checkTypeReferenceOrImport(node);
}

function checkPrivateNameType(node: PrivateNameTypeNode) {
const containingClass = getContainingClass(node);
if (containingClass === undefined) {
grammarErrorOnNode(node.name, Diagnostics.Private_identifiers_are_not_allowed_outside_class_bodies);
}
}

function checkNamedTupleMember(node: NamedTupleMember) {
if (node.dotDotDotToken && node.questionToken) {
grammarErrorOnNode(node, Diagnostics.A_tuple_member_cannot_be_both_optional_and_rest);
Expand Down Expand Up @@ -48293,6 +48329,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return checkTemplateLiteralType(node as TemplateLiteralTypeNode);
case SyntaxKind.ImportType:
return checkImportType(node as ImportTypeNode);
case SyntaxKind.PrivateNameType:
return checkPrivateNameType(node as PrivateNameTypeNode);
case SyntaxKind.NamedTupleMember:
return checkNamedTupleMember(node as NamedTupleMember);
case SyntaxKind.JSDocAugmentsTag:
Expand Down
7 changes: 7 additions & 0 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,7 @@ import {
PrinterOptions,
PrintHandlers,
PrivateIdentifier,
PrivateNameTypeNode,
PropertyAccessExpression,
PropertyAssignment,
PropertyDeclaration,
Expand Down Expand Up @@ -1655,6 +1656,8 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
return emitTemplateTypeSpan(node as TemplateLiteralTypeSpan);
case SyntaxKind.ImportType:
return emitImportTypeNode(node as ImportTypeNode);
case SyntaxKind.PrivateNameType:
return emitPrivateNameType(node as PrivateNameTypeNode);

// Binding patterns
case SyntaxKind.ObjectBindingPattern:
Expand Down Expand Up @@ -2560,6 +2563,10 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
emitTypeArguments(node, node.typeArguments);
}

function emitPrivateNameType(node: PrivateNameTypeNode) {
emitExpression(node.name);
}

//
// Binding patterns
//
Expand Down
18 changes: 18 additions & 0 deletions src/compiler/factory/nodeFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,7 @@ import {
PrefixUnaryOperator,
PrimaryExpression,
PrivateIdentifier,
PrivateNameTypeNode,
PrologueDirective,
PropertyAccessChain,
PropertyAccessExpression,
Expand Down Expand Up @@ -621,6 +622,8 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
updateLiteralTypeNode,
createTemplateLiteralType,
updateTemplateLiteralType,
createPrivateNameTypeNode,
updatePrivateNameTypeNode,
createObjectBindingPattern,
updateObjectBindingPattern,
createArrayBindingPattern,
Expand Down Expand Up @@ -2769,6 +2772,21 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
: node;
}

// @api
function createPrivateNameTypeNode(name: PrivateIdentifier) {
const node = createBaseNode<PrivateNameTypeNode>(SyntaxKind.PrivateNameType);
node.name = name;
node.transformFlags = TransformFlags.ContainsTypeScript;
return node;
}

// @api
function updatePrivateNameTypeNode(node: PrivateNameTypeNode, name: PrivateIdentifier) {
return node.name !== name
? update(createPrivateNameTypeNode(name), node)
: node;
}

//
// Binding Patterns
//
Expand Down
5 changes: 5 additions & 0 deletions src/compiler/factory/nodeTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ import {
PostfixUnaryExpression,
PrefixUnaryExpression,
PrivateIdentifier,
PrivateNameTypeNode,
PropertyAccessExpression,
PropertyAssignment,
PropertyDeclaration,
Expand Down Expand Up @@ -557,6 +558,10 @@ export function isTemplateLiteralTypeNode(node: Node): node is TemplateLiteralTy
return node.kind === SyntaxKind.TemplateLiteralType;
}

export function isPrivateNameTypeNode(node: Node): node is PrivateNameTypeNode {
return node.kind === SyntaxKind.PrivateNameType;
}

// Binding patterns

export function isObjectBindingPattern(node: Node): node is ObjectBindingPattern {
Expand Down
10 changes: 10 additions & 0 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,7 @@ import {
PrefixUnaryOperator,
PrimaryExpression,
PrivateIdentifier,
PrivateNameTypeNode,
PropertyAccessEntityNameExpression,
PropertyAccessExpression,
PropertyAssignment,
Expand Down Expand Up @@ -718,6 +719,9 @@ const forEachChildTable: ForEachChildTable = {
[SyntaxKind.LiteralType]: function forEachChildInLiteralType<T>(node: LiteralTypeNode, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray<Node>) => T | undefined): T | undefined {
return visitNode(cbNode, node.literal);
},
[SyntaxKind.PrivateNameType]: function forEachChildInPrivateNameType<T>(node: PrivateNameTypeNode, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray<Node>) => T | undefined): T | undefined {
return visitNode(cbNode, node.name);
},
[SyntaxKind.NamedTupleMember]: function forEachChildInNamedTupleMember<T>(node: NamedTupleMember, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray<Node>) => T | undefined): T | undefined {
return visitNode(cbNode, node.dotDotDotToken) ||
visitNode(cbNode, node.name) ||
Expand Down Expand Up @@ -4692,6 +4696,7 @@ namespace Parser {
case SyntaxKind.AssertsKeyword:
case SyntaxKind.NoSubstitutionTemplateLiteral:
case SyntaxKind.TemplateHead:
case SyntaxKind.PrivateIdentifier:
return true;
case SyntaxKind.FunctionKeyword:
return !inStartOfParameter;
Expand Down Expand Up @@ -4939,6 +4944,11 @@ namespace Parser {
if (contextFlags & NodeFlags.TypeExcludesFlags) {
return doOutsideOfContext(NodeFlags.TypeExcludesFlags, parseType);
}
if (token() === SyntaxKind.PrivateIdentifier) {
const pos = getNodePos();
const name = parsePrivateIdentifier();
return finishNode(factory.createPrivateNameTypeNode(name), pos);
}
if (isStartOfFunctionTypeOrConstructorType()) {
return parseFunctionOrConstructorType();
}
Expand Down
15 changes: 15 additions & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ export const enum SyntaxKind {
TemplateLiteralType,
TemplateLiteralTypeSpan,
ImportType,
PrivateNameType,
// Binding patterns
ObjectBindingPattern,
ArrayBindingPattern,
Expand Down Expand Up @@ -721,6 +722,7 @@ export type TypeNodeSyntaxKind =
| SyntaxKind.TemplateLiteralType
| SyntaxKind.TemplateLiteralTypeSpan
| SyntaxKind.ImportType
| SyntaxKind.PrivateNameType
| SyntaxKind.ExpressionWithTypeArguments
| SyntaxKind.JSDocTypeExpression
| SyntaxKind.JSDocAllType
Expand Down Expand Up @@ -1096,6 +1098,7 @@ export type HasChildren =
| LiteralTypeNode
| TemplateLiteralTypeNode
| TemplateLiteralTypeSpan
| PrivateNameTypeNode
| ObjectBindingPattern
| ArrayBindingPattern
| BindingElement
Expand Down Expand Up @@ -2383,6 +2386,11 @@ export interface TemplateLiteralTypeSpan extends TypeNode {
readonly literal: TemplateMiddle | TemplateTail;
}

export interface PrivateNameTypeNode extends TypeNode {
readonly kind: SyntaxKind.PrivateNameType;
readonly name: PrivateIdentifier;
}

// Note: 'brands' in our syntax nodes serve to give us a small amount of nominal typing.
// Consider 'Expression'. Without the brand, 'Expression' is actually no different
// (structurally) than 'Node'. Because of this you can pass any Node to a function that
Expand Down Expand Up @@ -6300,6 +6308,8 @@ export const enum TypeFlags {
Reserved1 = 1 << 29, // Used by union/intersection type construction
/** @internal */
Reserved2 = 1 << 30, // Used by union/intersection type construction
/** @internal */
PrivateNameType = 1 << 31,

/** @internal */
AnyOrUnknown = Any | Unknown,
Expand Down Expand Up @@ -6438,6 +6448,9 @@ export interface NumberLiteralType extends LiteralType {
export interface BigIntLiteralType extends LiteralType {
value: PseudoBigInt;
}
export interface PrivateNameType extends StringLiteralType {
symbol: Symbol;
}

// Enum types (TypeFlags.Enum)
export interface EnumType extends FreshableType {
Expand Down Expand Up @@ -8853,6 +8866,8 @@ export interface NodeFactory {
updateLiteralTypeNode(node: LiteralTypeNode, literal: LiteralTypeNode["literal"]): LiteralTypeNode;
createTemplateLiteralType(head: TemplateHead, templateSpans: readonly TemplateLiteralTypeSpan[]): TemplateLiteralTypeNode;
updateTemplateLiteralType(node: TemplateLiteralTypeNode, head: TemplateHead, templateSpans: readonly TemplateLiteralTypeSpan[]): TemplateLiteralTypeNode;
createPrivateNameTypeNode(name: PrivateIdentifier): PrivateNameTypeNode;
updatePrivateNameTypeNode(node: PrivateNameTypeNode, name: PrivateIdentifier): PrivateNameTypeNode;

//
// Binding Patterns
Expand Down
10 changes: 7 additions & 3 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,7 @@ import {
PrinterOptions,
PrintHandlers,
PrivateIdentifier,
PrivateNameType,
ProjectReference,
PrologueDirective,
PropertyAccessEntityNameExpression,
Expand Down Expand Up @@ -11057,18 +11058,21 @@ export function intrinsicTagNameToString(node: Identifier | JsxNamespacedName):
* Indicates whether a type can be used as a property name.
* @internal
*/
export function isTypeUsableAsPropertyName(type: Type): type is StringLiteralType | NumberLiteralType | UniqueESSymbolType {
return !!(type.flags & TypeFlags.StringOrNumberLiteralOrUnique);
export function isTypeUsableAsPropertyName(type: Type): type is StringLiteralType | NumberLiteralType | UniqueESSymbolType | PrivateNameType {
return !!(type.flags & (TypeFlags.StringOrNumberLiteralOrUnique | TypeFlags.PrivateNameType));
}

/**
* Gets the symbolic name for a member from its type.
* @internal
*/
export function getPropertyNameFromType(type: StringLiteralType | NumberLiteralType | UniqueESSymbolType): __String {
export function getPropertyNameFromType(type: StringLiteralType | NumberLiteralType | UniqueESSymbolType | PrivateNameType): __String {
if (type.flags & TypeFlags.UniqueESSymbol) {
return (type as UniqueESSymbolType).escapedName;
}
if (type.flags & TypeFlags.PrivateNameType) {
return (type as PrivateNameType).symbol.escapedName;
}
if (type.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral)) {
return escapeLeadingUnderscores("" + (type as StringLiteralType | NumberLiteralType).value);
}
Expand Down
8 changes: 8 additions & 0 deletions src/compiler/visitorPublic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ import {
isObjectLiteralElementLike,
isOptionalChain,
isParameter,
isPrivateIdentifier,
isPropertyAccessChain,
isPropertyName,
isQuestionDotToken,
Expand Down Expand Up @@ -978,6 +979,13 @@ const visitEachChildTable: VisitEachChildTable = {
);
},

[SyntaxKind.PrivateNameType]: function visitEachChildOfPrivateIdentifier(node, visitor, context, _nodesVisitor, nodeVisitor, _tokenVisitor) {
return context.factory.updatePrivateNameTypeNode(
node,
Debug.checkDefined(nodeVisitor(node.name, visitor, isPrivateIdentifier)),
);
},

// Binding patterns
[SyntaxKind.ObjectBindingPattern]: function visitEachChildOfObjectBindingPattern(node, visitor, context, nodesVisitor, _nodeVisitor, _tokenVisitor) {
return context.factory.updateObjectBindingPattern(
Expand Down
Loading