Skip to content

Feature: Self-Type-Checking Types #52088

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

Closed
wants to merge 31 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
188b9bb
use bigint for type flags
devanshj Dec 19, 2022
5540ad5
self-type-checking types
devanshj Dec 19, 2022
34c8a80
add `NeverWithError` and `Print` type constructs
devanshj Dec 19, 2022
c4d98c3
fix bad comment
devanshj Dec 27, 2022
78d74a9
add new tests
devanshj Jan 3, 2023
f3dc6f5
make minor changes to some tests
devanshj Jan 3, 2023
ff00503
accept new baselines
devanshj Jan 3, 2023
a41f15b
fix lint
devanshj Jan 3, 2023
ea6d257
remove debug logging
devanshj Jan 3, 2023
69b7959
resolve conflicts
devanshj Jan 3, 2023
389833b
fix code to accomodate #51682
devanshj Jan 3, 2023
0e38395
allow intrinsic keyword in new intrinsic types
devanshj Jan 3, 2023
c10528b
add self as a global keyword in fourslash
devanshj Jan 3, 2023
e3cff6b
add "self" to more places in fourslash
devanshj Jan 3, 2023
9c076aa
remove accidently added `Map` global in fourslash
devanshj Jan 4, 2023
afdc60e
remove a possibly false positive debug assertion
devanshj Jan 4, 2023
a1fb821
remove a todo as it got fixed after resolving conflicts
devanshj Jan 4, 2023
2ee5f95
accept new baseline for new tests after resolving conflict
devanshj Jan 4, 2023
a805ae9
create a new type construct "Selfed"
devanshj Jan 6, 2023
3e9e52e
accept baseline
devanshj Jan 6, 2023
bf8b3ab
fix completions order
devanshj Jan 6, 2023
90c9032
accept completions and other chore baseline
devanshj Jan 6, 2023
bc980f1
fix lint
devanshj Jan 6, 2023
8ffb161
fix external .d.ts
devanshj Jan 7, 2023
d3e64a5
recognise "self" as a type at missing places
devanshj Jan 7, 2023
8b0e980
accept completions baseline
devanshj Jan 7, 2023
0eef6df
accept external .d.ts baseline
devanshj Jan 7, 2023
9ecf8a8
make self instantiation more resilient
devanshj Jan 7, 2023
9815389
create an emit flag to not escape string literals
devanshj Jan 7, 2023
ecfa9ed
fix lint
devanshj Jan 7, 2023
303ea00
accept new public api baseline
devanshj Jan 7, 2023
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
2 changes: 1 addition & 1 deletion Herebyfile.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ function createBundler(entrypoint, outfile, taskOptions = {}) {
bundle: true,
outfile,
platform: "node",
target: "es2018",
target: "es2020",
format: "cjs",
sourcemap: "linked",
sourcesContent: false,
Expand Down
440 changes: 346 additions & 94 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/compiler/debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,7 @@ export namespace Debug {
}

export function formatTypeFlags(flags: TypeFlags | undefined): string {
// @ts-ignore TODO
return formatEnum(flags, (ts as any).TypeFlags, /*isFlags*/ true);
}

Expand Down
4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -7612,5 +7612,9 @@
"The value '{0}' cannot be used here.": {
"category": "Error",
"code": 18050
},
"{0}": {
"category": "Error",
"code": 18051
}
}
3 changes: 2 additions & 1 deletion src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5460,7 +5460,8 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
const textSourceNode = (node as StringLiteral).textSourceNode!;
if (isIdentifier(textSourceNode) || isPrivateIdentifier(textSourceNode) || isNumericLiteral(textSourceNode)) {
const text = isNumericLiteral(textSourceNode) ? textSourceNode.text : getTextOfNode(textSourceNode);
return jsxAttributeEscape ? `"${escapeJsxAttributeString(text)}"` :
return getEmitFlags(node) & EmitFlags.NoStringEscaping ? `"${text}"` :
jsxAttributeEscape ? `"${escapeJsxAttributeString(text)}"` :
neverAsciiEscape || (getEmitFlags(node) & EmitFlags.NoAsciiEscaping) ? `"${escapeString(text)}"` :
`"${escapeNonAsciiString(text)}"`;
}
Expand Down
1 change: 1 addition & 0 deletions src/compiler/factory/nodeFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1348,6 +1348,7 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
case SyntaxKind.SymbolKeyword:
case SyntaxKind.VoidKeyword:
case SyntaxKind.UnknownKeyword:
case SyntaxKind.SelfKeyword:
case SyntaxKind.UndefinedKeyword: // `undefined` is an Identifier in the expression case.
transformFlags = TransformFlags.ContainsTypeScript;
break;
Expand Down
2 changes: 2 additions & 0 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4467,6 +4467,7 @@ namespace Parser {
case SyntaxKind.UndefinedKeyword:
case SyntaxKind.NeverKeyword:
case SyntaxKind.ObjectKeyword:
case SyntaxKind.SelfKeyword:
// If these are followed by a dot, then parse these out as a dotted type reference instead.
return tryParse(parseKeywordAndNoDot) || parseTypeReference();
case SyntaxKind.AsteriskEqualsToken:
Expand Down Expand Up @@ -4542,6 +4543,7 @@ namespace Parser {
case SyntaxKind.ThisKeyword:
case SyntaxKind.TypeOfKeyword:
case SyntaxKind.NeverKeyword:
case SyntaxKind.SelfKeyword:
case SyntaxKind.OpenBraceToken:
case SyntaxKind.OpenBracketToken:
case SyntaxKind.LessThanToken:
Expand Down
1 change: 1 addition & 0 deletions src/compiler/scanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ export const textToKeywordObj: MapLike<KeywordSyntaxKind> = {
return: SyntaxKind.ReturnKeyword,
satisfies: SyntaxKind.SatisfiesKeyword,
set: SyntaxKind.SetKeyword,
self: SyntaxKind.SelfKeyword,
static: SyntaxKind.StaticKeyword,
string: SyntaxKind.StringKeyword,
super: SyntaxKind.SuperKeyword,
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/tracing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ export namespace tracingEnabled {

// It's slow to compute the display text, so skip it unless it's really valuable (or cheap)
let display: string | undefined;
if ((objectFlags & ObjectFlags.Anonymous) | (type.flags & TypeFlags.Literal)) {
if ((objectFlags & ObjectFlags.Anonymous) || (type.flags & TypeFlags.Literal)) {
try {
display = type.checker?.typeToString(type);
}
Expand Down
1 change: 1 addition & 0 deletions src/compiler/transformers/ts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,7 @@ export function transformTypeScript(context: TransformationContext) {
case SyntaxKind.NeverKeyword:
case SyntaxKind.VoidKeyword:
case SyntaxKind.SymbolKeyword:
case SyntaxKind.SelfKeyword:
case SyntaxKind.ConstructorType:
case SyntaxKind.FunctionType:
case SyntaxKind.TypeQuery:
Expand Down
194 changes: 112 additions & 82 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ export const enum SyntaxKind {
ObjectKeyword,
SatisfiesKeyword,
SetKeyword,
SelfKeyword,
StringKeyword,
SymbolKeyword,
TypeKeyword,
Expand Down Expand Up @@ -635,6 +636,7 @@ export type KeywordSyntaxKind =
| SyntaxKind.ReturnKeyword
| SyntaxKind.SatisfiesKeyword
| SyntaxKind.SetKeyword
| SyntaxKind.SelfKeyword
| SyntaxKind.StaticKeyword
| SyntaxKind.StringKeyword
| SyntaxKind.SuperKeyword
Expand Down Expand Up @@ -686,6 +688,7 @@ export type KeywordTypeSyntaxKind =
| SyntaxKind.SymbolKeyword
| SyntaxKind.UndefinedKeyword
| SyntaxKind.UnknownKeyword
| SyntaxKind.SelfKeyword
| SyntaxKind.VoidKeyword
;

Expand Down Expand Up @@ -5290,6 +5293,7 @@ export const enum NodeBuilderFlags {
UseSingleQuotesForStringLiteralType = 1 << 28, // Use single quotes for string literal type
NoTypeReduction = 1 << 29, // Don't call getReducedType
OmitThisParameter = 1 << 25,
NoStringLiteralEscaping = 1 << 31,

// Error handling
AllowThisInObjectLiteral = 1 << 15,
Expand Down Expand Up @@ -5351,12 +5355,14 @@ export const enum TypeFormatFlags {
InFirstTypeArgument = 1 << 22, // Writing first type argument of the instantiated type
InTypeAlias = 1 << 23, // Writing type in type alias declaration

NoStringLiteralEscaping = 1 << 31,

/** @deprecated */ WriteOwnNameForAnyLike = 0, // Does nothing

NodeBuilderFlagsMask = NoTruncation | WriteArrayAsGenericType | UseStructuralFallback | WriteTypeArgumentsOfSignature |
UseFullyQualifiedType | SuppressAnyReturnType | MultilineObjectLiterals | WriteClassExpressionAsTypeLiteral |
UseTypeOfFunction | OmitParameterModifiers | UseAliasDefinedOutsideCurrentScope | AllowUniqueESSymbolType | InTypeAlias |
UseSingleQuotesForStringLiteralType | NoTypeReduction | OmitThisParameter
UseSingleQuotesForStringLiteralType | NoTypeReduction | OmitThisParameter | NoStringLiteralEscaping
}

export const enum SymbolFormatFlags {
Expand Down Expand Up @@ -5770,6 +5776,7 @@ export interface SymbolLinks {
uniqueESSymbolType?: Type; // UniqueESSymbol type for a symbol
declaredType?: Type; // Type of class, interface, enum, type alias, or type parameter
typeParameters?: TypeParameter[]; // Type parameters of type alias (undefined if non-generic)
selfType?: Type // Self type parameter of type alias (undefined if not used in declaration)
outerTypeParameters?: TypeParameter[]; // Outer type parameters of anonymous object type
instantiations?: Map<string, Type>; // Instantiations of generic type alias (undefined if non-generic)
aliasSymbol?: Symbol; // Alias associated with generic type alias instantiation
Expand Down Expand Up @@ -5994,97 +6001,102 @@ export interface SerializedTypeEntry {
addedLength: number;
}

export const enum TypeFlags {
Any = 1 << 0,
Unknown = 1 << 1,
String = 1 << 2,
Number = 1 << 3,
Boolean = 1 << 4,
Enum = 1 << 5, // Numeric computed enum member value
BigInt = 1 << 6,
StringLiteral = 1 << 7,
NumberLiteral = 1 << 8,
BooleanLiteral = 1 << 9,
EnumLiteral = 1 << 10, // Always combined with StringLiteral, NumberLiteral, or Union
BigIntLiteral = 1 << 11,
ESSymbol = 1 << 12, // Type of symbol primitive introduced in ES6
UniqueESSymbol = 1 << 13, // unique symbol
Void = 1 << 14,
Undefined = 1 << 15,
Null = 1 << 16,
Never = 1 << 17, // Never type
TypeParameter = 1 << 18, // Type parameter
Object = 1 << 19, // Object type
Union = 1 << 20, // Union (T | U)
Intersection = 1 << 21, // Intersection (T & U)
Index = 1 << 22, // keyof T
IndexedAccess = 1 << 23, // T[K]
Conditional = 1 << 24, // T extends U ? X : Y
Substitution = 1 << 25, // Type parameter substitution
NonPrimitive = 1 << 26, // intrinsic object type
TemplateLiteral = 1 << 27, // Template literal type
StringMapping = 1 << 28, // Uppercase/Lowercase type

/** @internal */
AnyOrUnknown = Any | Unknown,
/** @internal */
Nullable = Undefined | Null,
Literal = StringLiteral | NumberLiteral | BigIntLiteral | BooleanLiteral,
Unit = Literal | UniqueESSymbol | Nullable,
StringOrNumberLiteral = StringLiteral | NumberLiteral,
/** @internal */
StringOrNumberLiteralOrUnique = StringLiteral | NumberLiteral | UniqueESSymbol,
/** @internal */
DefinitelyFalsy = StringLiteral | NumberLiteral | BigIntLiteral | BooleanLiteral | Void | Undefined | Null,
PossiblyFalsy = DefinitelyFalsy | String | Number | BigInt | Boolean,
/** @internal */
Intrinsic = Any | Unknown | String | Number | BigInt | Boolean | BooleanLiteral | ESSymbol | Void | Undefined | Null | Never | NonPrimitive,
/** @internal */
Primitive = String | Number | BigInt | Boolean | Enum | EnumLiteral | ESSymbol | Void | Undefined | Null | Literal | UniqueESSymbol,
StringLike = String | StringLiteral | TemplateLiteral | StringMapping,
NumberLike = Number | NumberLiteral | Enum,
BigIntLike = BigInt | BigIntLiteral,
BooleanLike = Boolean | BooleanLiteral,
EnumLike = Enum | EnumLiteral,
ESSymbolLike = ESSymbol | UniqueESSymbol,
VoidLike = Void | Undefined,
/** @internal */
DefinitelyNonNullable = StringLike | NumberLike | BigIntLike | BooleanLike | EnumLike | ESSymbolLike | Object | NonPrimitive,
/** @internal */
DisjointDomains = NonPrimitive | StringLike | NumberLike | BigIntLike | BooleanLike | ESSymbolLike | VoidLike | Null,
UnionOrIntersection = Union | Intersection,
StructuredType = Object | Union | Intersection,
TypeVariable = TypeParameter | IndexedAccess,
InstantiableNonPrimitive = TypeVariable | Conditional | Substitution,
InstantiablePrimitive = Index | TemplateLiteral | StringMapping,
Instantiable = InstantiableNonPrimitive | InstantiablePrimitive,
StructuredOrInstantiable = StructuredType | Instantiable,
/** @internal */
ObjectFlagsType = Any | Nullable | Never | Object | Union | Intersection,
/** @internal */
Simplifiable = IndexedAccess | Conditional,
/** @internal */
Singleton = Any | Unknown | String | Number | Boolean | BigInt | ESSymbol | Void | Undefined | Null | Never | NonPrimitive,
export type TypeFlags = (typeof TypeFlags)[keyof typeof TypeFlags];
export const TypeFlags = new class TypeFlags {
Any = 1n << 0n;
Unknown = 1n << 1n;
String = 1n << 2n;
Number = 1n << 3n;
Boolean = 1n << 4n;
Enum = 1n << 5n; // Numeric computed enum member value
BigInt = 1n << 6n;
StringLiteral = 1n << 7n;
NumberLiteral = 1n << 8n;
BooleanLiteral = 1n << 9n;
EnumLiteral = 1n << 10n; // Always combined with StringLiteral, NumberLiteral, or Union
BigIntLiteral = 1n << 11n;
ESSymbol = 1n << 12n; // Type of symbol primitive introduced in ES6
UniqueESSymbol = 1n << 13n; // unique symbol
Void = 1n << 14n;
Undefined = 1n << 15n;
Null = 1n << 16n;
Never = 1n << 17n; // Never type
TypeParameter = 1n << 18n; // Type parameter
Object = 1n << 19n; // Object type
Union = 1n << 20n; // Union (T | U)
Intersection = 1n << 21n; // Intersection (T & U)
Index = 1n << 22n; // keyof T
IndexedAccess = 1n << 23n; // T[K]
Conditional = 1n << 24n; // T extends U ? X : Y
Substitution = 1n << 25n; // Type parameter substitution
NonPrimitive = 1n << 26n; // intrinsic object type
TemplateLiteral = 1n << 27n; // Template literal type
StringMapping = 1n << 28n; // Uppercase/Lowercase type
Self = 1n << 29n;
Selfed = 1n << 30n;
NeverWithError = 1n << 31n;
Print = 1n << 32n;

/** @internal */
AnyOrUnknown = this.Any | this.Unknown;
/** @internal */
Nullable = this.Undefined | this.Null;
Literal = this.StringLiteral | this.NumberLiteral | this.BigIntLiteral | this.BooleanLiteral;
Unit = this.Literal | this.UniqueESSymbol | this.Nullable;
StringOrNumberLiteral = this.StringLiteral | this.NumberLiteral;
/** @internal */
StringOrNumberLiteralOrUnique = this.StringLiteral | this.NumberLiteral | this.UniqueESSymbol;
/** @internal */
DefinitelyFalsy = this.StringLiteral | this.NumberLiteral | this.BigIntLiteral | this.BooleanLiteral | this.Void | this.Undefined | this.Null;
PossiblyFalsy = this.DefinitelyFalsy | this.String | this.Number | this.BigInt | this.Boolean;
/** @internal */
Intrinsic = this.Any | this.Unknown | this.String | this.Number | this.BigInt | this.Boolean | this.BooleanLiteral | this.ESSymbol | this.Void | this.Undefined | this.Null | this.Never | this.NonPrimitive;
/** @internal */
Primitive = this.String | this.Number | this.BigInt | this.Boolean | this.Enum | this.EnumLiteral | this.ESSymbol | this.Void | this.Undefined | this.Null | this.Literal | this.UniqueESSymbol;
StringLike = this.String | this.StringLiteral | this.TemplateLiteral | this.StringMapping | this.Print;
NumberLike = this.Number | this.NumberLiteral | this.Enum;
BigIntLike = this.BigInt | this.BigIntLiteral;
BooleanLike = this.Boolean | this.BooleanLiteral;
EnumLike = this.Enum | this.EnumLiteral;
ESSymbolLike = this.ESSymbol | this.UniqueESSymbol;
VoidLike = this.Void | this.Undefined;
/** @internal */
DefinitelyNonNullable = this.StringLike | this.NumberLike | this.BigIntLike | this.BooleanLike | this.EnumLike | this.ESSymbolLike | this.Object | this.NonPrimitive;
/** @internal */
DisjointDomains = this.NonPrimitive | this.StringLike | this.NumberLike | this.BigIntLike | this.BooleanLike | this.ESSymbolLike | this.VoidLike | this.Null;
UnionOrIntersection = this.Union | this.Intersection;
StructuredType = this.Object | this.Union | this.Intersection;
TypeVariable = this.TypeParameter | this.IndexedAccess;
InstantiableNonPrimitive = this.TypeVariable | this.Conditional | this.Substitution | this.Selfed | this.NeverWithError;
InstantiablePrimitive = this.Index | this.TemplateLiteral | this.StringMapping | this.Print;
Instantiable = this.InstantiableNonPrimitive | this.InstantiablePrimitive;
StructuredOrInstantiable = this.StructuredType | this.Instantiable;
/** @internal */
ObjectFlagsType = this.Any | this.Nullable | this.Never | this.Object | this.Union | this.Intersection;
/** @internal */
Simplifiable = this.IndexedAccess | this.Conditional;
/** @internal */
Singleton = this.Any | this.Unknown | this.String | this.Number | this.Boolean | this.BigInt | this.ESSymbol | this.Void | this.Undefined | this.Null | this.Never | this.NonPrimitive;
// 'Narrowable' types are types where narrowing actually narrows.
// This *should* be every type other than null, undefined, void, and never
Narrowable = Any | Unknown | StructuredOrInstantiable | StringLike | NumberLike | BigIntLike | BooleanLike | ESSymbol | UniqueESSymbol | NonPrimitive,
Narrowable = this.Any | this.Unknown | this.StructuredOrInstantiable | this.StringLike | this.NumberLike | this.BigIntLike | this.BooleanLike | this.ESSymbol | this.UniqueESSymbol | this.NonPrimitive;
// The following flags are aggregated during union and intersection type construction
/** @internal */
IncludesMask = Any | Unknown | Primitive | Never | Object | Union | Intersection | NonPrimitive | TemplateLiteral,
IncludesMask = this.Any | this.Unknown | this.Primitive | this.Never | this.Object | this.Union | this.Intersection | this.NonPrimitive | this.TemplateLiteral;
// The following flags are used for different purposes during union and intersection type construction
/** @internal */
IncludesMissingType = TypeParameter,
IncludesMissingType = this.TypeParameter;
/** @internal */
IncludesNonWideningType = Index,
IncludesNonWideningType = this.Index;
/** @internal */
IncludesWildcard = IndexedAccess,
IncludesWildcard = this.IndexedAccess;
/** @internal */
IncludesEmptyObject = Conditional,
IncludesEmptyObject = this.Conditional;
/** @internal */
IncludesInstantiable = Substitution,
IncludesInstantiable = this.Substitution;
/** @internal */
NotPrimitiveUnion = Any | Unknown | Enum | Void | Never | Object | Intersection | IncludesInstantiable,
}
NotPrimitiveUnion = this.Any | this.Unknown | this.Enum | this.Void | this.Never | this.Object | this.Intersection | this.IncludesInstantiable;
}();

export type DestructuringPattern = BindingPattern | ObjectLiteralExpression | ArrayLiteralExpression;

Expand Down Expand Up @@ -6591,7 +6603,6 @@ export interface StringMappingType extends InstantiableType {
symbol: Symbol;
type: Type;
}

// Type parameter substitution (TypeFlags.Substitution)
// Substitution types are created for type parameters or indexed access types that occur in the
// true branch of a conditional type. For example, in 'T extends string ? Foo<T> : Bar<T>', the
Expand All @@ -6604,6 +6615,24 @@ export interface SubstitutionType extends InstantiableType {
constraint: Type; // Constraint that target type is known to satisfy
}

export interface SelfedType extends InstantiableType {
type: Type
selfType: Type
instantiations: Map<string, Type>
}

/** @internal */
export interface NeverWithErrorType extends InstantiableType, IntrinsicType {
errorType: Type
}

export interface PrintType extends InstantiableType {
type: Type
flagType: Type
resolvedStringLiteralType: StringLiteralType
}


/** @internal */
export const enum JsxReferenceKind {
Component,
Expand Down Expand Up @@ -7890,6 +7919,7 @@ export const enum EmitFlags {
/** @internal */ IgnoreSourceNewlines = 1 << 28, // Overrides `printerOptions.preserveSourceNewlines` to print this node (and all descendants) with default whitespace.
/** @internal */ Immutable = 1 << 29, // Indicates a node is a singleton intended to be reused in multiple locations. Any attempt to make further changes to the node will result in an error.
/** @internal */ IndirectCall = 1 << 30, // Emit CallExpression as an indirect call: `(0, f)()`
NoStringEscaping = 1 << 31
}

export interface EmitHelperBase {
Expand Down
Loading