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

Add NoInfer intrinsic type #52968

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
78 changes: 55 additions & 23 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -845,6 +845,7 @@ import {
nodeModulesPathPart,
nodeStartsNewLexicalEnvironment,
NodeWithTypeArguments,
NoInferType,
NonNullChain,
NonNullExpression,
not,
Expand Down Expand Up @@ -1328,18 +1329,18 @@ const enum MinArgumentCountFlags {
VoidIsNonOptional = 1 << 1,
}

const enum IntrinsicTypeKind {
const enum StringMappingTypeKind {
Uppercase,
Lowercase,
Capitalize,
Uncapitalize
}

const intrinsicTypeKinds: ReadonlyMap<string, IntrinsicTypeKind> = new Map(Object.entries({
Uppercase: IntrinsicTypeKind.Uppercase,
Lowercase: IntrinsicTypeKind.Lowercase,
Capitalize: IntrinsicTypeKind.Capitalize,
Uncapitalize: IntrinsicTypeKind.Uncapitalize
const stringMappingTypeKinds: ReadonlyMap<string, StringMappingTypeKind> = new Map(Object.entries({
Uppercase: StringMappingTypeKind.Uppercase,
Lowercase: StringMappingTypeKind.Lowercase,
Capitalize: StringMappingTypeKind.Capitalize,
Uncapitalize: StringMappingTypeKind.Uncapitalize
}));

const SymbolLinks = class implements SymbolLinks {
Expand Down Expand Up @@ -1876,7 +1877,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
var enumLiteralTypes = new Map<string, LiteralType>();
var indexedAccessTypes = new Map<string, IndexedAccessType>();
var templateLiteralTypes = new Map<string, TemplateLiteralType>();
var stringMappingTypes = new Map<string, StringMappingType>();
var intrinsicWrapperTypes = new Map<string, StringMappingType | NoInferType>();
var substitutionTypes = new Map<string, SubstitutionType>();
var subtypeReductionCache = new Map<string, Type[]>();
var decoratorContextOverrideTypeCache = new Map<string, Type>();
Expand Down Expand Up @@ -6526,6 +6527,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
const typeNode = typeToTypeNodeHelper((type as StringMappingType).type, context);
return symbolToTypeNode((type as StringMappingType).symbol, context, SymbolFlags.Type, [typeNode]);
}
if (type.flags & TypeFlags.NoInfer) {
const typeNode = typeToTypeNodeHelper((type as NoInferType).type, context);
return symbolToTypeNode((type as NoInferType).symbol, context, SymbolFlags.Type, [typeNode]);
}
if (type.flags & TypeFlags.IndexedAccess) {
const objectTypeNode = typeToTypeNodeHelper((type as IndexedAccessType).objectType, context);
const indexTypeNode = typeToTypeNodeHelper((type as IndexedAccessType).indexType, context);
Expand Down Expand Up @@ -13727,6 +13732,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
const constraint = getBaseConstraint((t as StringMappingType).type);
return constraint && constraint !== (t as StringMappingType).type ? getStringMappingType((t as StringMappingType).symbol, constraint) : stringType;
}
if (t.flags & TypeFlags.NoInfer) {
return getBaseConstraint((t as NoInferType).type);
}
if (t.flags & TypeFlags.IndexedAccess) {
if (isMappedTypeGenericIndexedAccess(t)) {
// For indexed access types of the form { [P in K]: E }[X], where K is non-generic and X is generic,
Expand Down Expand Up @@ -15168,8 +15176,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {

function getTypeAliasInstantiation(symbol: Symbol, typeArguments: readonly Type[] | undefined, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type {
const type = getDeclaredTypeOfSymbol(symbol);
if (type === intrinsicMarkerType && intrinsicTypeKinds.has(symbol.escapedName as string) && typeArguments && typeArguments.length === 1) {
return getStringMappingType(symbol, typeArguments[0]);
if (type === intrinsicMarkerType && typeArguments && typeArguments.length === 1) {
if (stringMappingTypeKinds.has(symbol.escapedName as string)) {
return getStringMappingType(symbol, typeArguments[0]);
}
return getNoInferType(symbol, typeArguments[0]);
}
const links = getSymbolLinks(symbol);
const typeParameters = links.typeParameters!;
Expand Down Expand Up @@ -17071,31 +17082,43 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
type;
}

function getNoInferType(symbol: Symbol, type: Type): Type {
if (!isGenericType(type)) {
return type;
}
const id = `${getSymbolId(symbol)},${getTypeId(type)}`;
let result = intrinsicWrapperTypes.get(id);
if (!result) {
intrinsicWrapperTypes.set(id, result = createNoInferType(symbol, type));
}
return result;
}

function applyStringMapping(symbol: Symbol, str: string) {
switch (intrinsicTypeKinds.get(symbol.escapedName as string)) {
case IntrinsicTypeKind.Uppercase: return str.toUpperCase();
case IntrinsicTypeKind.Lowercase: return str.toLowerCase();
case IntrinsicTypeKind.Capitalize: return str.charAt(0).toUpperCase() + str.slice(1);
case IntrinsicTypeKind.Uncapitalize: return str.charAt(0).toLowerCase() + str.slice(1);
switch (stringMappingTypeKinds.get(symbol.escapedName as string)) {
case StringMappingTypeKind.Uppercase: return str.toUpperCase();
case StringMappingTypeKind.Lowercase: return str.toLowerCase();
case StringMappingTypeKind.Capitalize: return str.charAt(0).toUpperCase() + str.slice(1);
case StringMappingTypeKind.Uncapitalize: return str.charAt(0).toLowerCase() + str.slice(1);
}
return str;
}

function applyTemplateStringMapping(symbol: Symbol, texts: readonly string[], types: readonly Type[]): [texts: readonly string[], types: readonly Type[]] {
switch (intrinsicTypeKinds.get(symbol.escapedName as string)) {
case IntrinsicTypeKind.Uppercase: return [texts.map(t => t.toUpperCase()), types.map(t => getStringMappingType(symbol, t))];
case IntrinsicTypeKind.Lowercase: return [texts.map(t => t.toLowerCase()), types.map(t => getStringMappingType(symbol, t))];
case IntrinsicTypeKind.Capitalize: return [texts[0] === "" ? texts : [texts[0].charAt(0).toUpperCase() + texts[0].slice(1), ...texts.slice(1)], texts[0] === "" ? [getStringMappingType(symbol, types[0]), ...types.slice(1)] : types];
case IntrinsicTypeKind.Uncapitalize: return [texts[0] === "" ? texts : [texts[0].charAt(0).toLowerCase() + texts[0].slice(1), ...texts.slice(1)], texts[0] === "" ? [getStringMappingType(symbol, types[0]), ...types.slice(1)] : types];
switch (stringMappingTypeKinds.get(symbol.escapedName as string)) {
case StringMappingTypeKind.Uppercase: return [texts.map(t => t.toUpperCase()), types.map(t => getStringMappingType(symbol, t))];
case StringMappingTypeKind.Lowercase: return [texts.map(t => t.toLowerCase()), types.map(t => getStringMappingType(symbol, t))];
case StringMappingTypeKind.Capitalize: return [texts[0] === "" ? texts : [texts[0].charAt(0).toUpperCase() + texts[0].slice(1), ...texts.slice(1)], texts[0] === "" ? [getStringMappingType(symbol, types[0]), ...types.slice(1)] : types];
case StringMappingTypeKind.Uncapitalize: return [texts[0] === "" ? texts : [texts[0].charAt(0).toLowerCase() + texts[0].slice(1), ...texts.slice(1)], texts[0] === "" ? [getStringMappingType(symbol, types[0]), ...types.slice(1)] : types];
}
return [texts, types];
}

function getStringMappingTypeForGenericType(symbol: Symbol, type: Type): Type {
const id = `${getSymbolId(symbol)},${getTypeId(type)}`;
let result = stringMappingTypes.get(id);
let result = intrinsicWrapperTypes.get(id);
if (!result) {
stringMappingTypes.set(id, result = createStringMappingType(symbol, type));
intrinsicWrapperTypes.set(id, result = createStringMappingType(symbol, type));
}
return result;
}
Expand All @@ -17106,6 +17129,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return result;
}

function createNoInferType(symbol: Symbol, type: Type) {
const result = createTypeWithSymbol(TypeFlags.NoInfer, symbol) as NoInferType;
result.type = type;
return result;
}

function createIndexedAccessType(objectType: Type, indexType: Type, accessFlags: AccessFlags, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined) {
const type = createType(TypeFlags.IndexedAccess) as IndexedAccessType;
type.objectType = objectType;
Expand Down Expand Up @@ -18931,6 +18960,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (flags & TypeFlags.StringMapping) {
return getStringMappingType((type as StringMappingType).symbol, instantiateType((type as StringMappingType).type, mapper));
}
if (flags & TypeFlags.NoInfer) {
return getNoInferType((type as NoInferType).symbol, instantiateType((type as NoInferType).type, mapper));
}
if (flags & TypeFlags.IndexedAccess) {
const newAliasSymbol = aliasSymbol || type.aliasSymbol;
const newAliasTypeArguments = aliasSymbol ? aliasTypeArguments : instantiateTypes(type.aliasTypeArguments, mapper);
Expand Down Expand Up @@ -24169,7 +24201,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
inferFromTypes(originalSource, originalTarget);

function inferFromTypes(source: Type, target: Type): void {
if (!couldContainTypeVariables(target)) {
if (!couldContainTypeVariables(target) || source.flags & TypeFlags.NoInfer) {
return;
}
if (source === wildcardType) {
Expand Down Expand Up @@ -43190,7 +43222,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
checkExportsOnMergedDeclarations(node);
checkTypeParameters(node.typeParameters);
if (node.type.kind === SyntaxKind.IntrinsicKeyword) {
if (!intrinsicTypeKinds.has(node.name.escapedText as string) || length(node.typeParameters) !== 1) {
if (!stringMappingTypeKinds.has(node.name.escapedText as string) || length(node.typeParameters) !== 1) {
error(node.type, Diagnostics.The_intrinsic_keyword_can_only_be_used_to_declare_compiler_provided_intrinsic_types);
}
}
Expand Down
8 changes: 7 additions & 1 deletion src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6039,6 +6039,7 @@ export const enum TypeFlags {
NonPrimitive = 1 << 26, // intrinsic object type
TemplateLiteral = 1 << 27, // Template literal type
StringMapping = 1 << 28, // Uppercase/Lowercase type
NoInfer = 1 << 29, // NoInfer type

/** @internal */
AnyOrUnknown = Any | Unknown,
Expand Down Expand Up @@ -6071,7 +6072,7 @@ export const enum TypeFlags {
UnionOrIntersection = Union | Intersection,
StructuredType = Object | Union | Intersection,
TypeVariable = TypeParameter | IndexedAccess,
InstantiableNonPrimitive = TypeVariable | Conditional | Substitution,
InstantiableNonPrimitive = TypeVariable | Conditional | Substitution | NoInfer,
InstantiablePrimitive = Index | TemplateLiteral | StringMapping,
Instantiable = InstantiableNonPrimitive | InstantiablePrimitive,
StructuredOrInstantiable = StructuredType | Instantiable,
Expand Down Expand Up @@ -6606,6 +6607,11 @@ export interface StringMappingType extends InstantiableType {
type: Type;
}

export interface NoInferType 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 Down
5 changes: 5 additions & 0 deletions src/lib/es5.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1629,6 +1629,11 @@ type Capitalize<S extends string> = intrinsic;
*/
type Uncapitalize<S extends string> = intrinsic;

/**
* Blocks the contained type from participating in type inference.
*/
type NoInfer<T> = intrinsic;

/**
* Marker for contextual 'this' type
*/
Expand Down
84 changes: 84 additions & 0 deletions tests/baselines/reference/noInfer.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
tests/cases/conformance/types/typeRelationships/typeInference/noInfer.ts(4,12): error TS2345: Argument of type '"bar"' is not assignable to parameter of type '"foo"'.
tests/cases/conformance/types/typeRelationships/typeInference/noInfer.ts(12,30): error TS2741: Property 'woof' is missing in type 'Animal' but required in type 'Dog'.
tests/cases/conformance/types/typeRelationships/typeInference/noInfer.ts(18,16): error TS2345: Argument of type '{ x: number; }' is not assignable to parameter of type '{ x: number; y: number; }'.
Property 'y' is missing in type '{ x: number; }' but required in type '{ x: number; y: number; }'.
tests/cases/conformance/types/typeRelationships/typeInference/noInfer.ts(23,22): error TS2345: Argument of type '{ x: number; y: number; }' is not assignable to parameter of type '{ x: number; }'.
Object literal may only specify known properties, and 'y' does not exist in type '{ x: number; }'.
tests/cases/conformance/types/typeRelationships/typeInference/noInfer.ts(24,14): error TS2345: Argument of type '{ x: number; y: number; }' is not assignable to parameter of type '{ x: number; }'.
Object literal may only specify known properties, and 'y' does not exist in type '{ x: number; }'.
tests/cases/conformance/types/typeRelationships/typeInference/noInfer.ts(32,14): error TS2345: Argument of type '{}' is not assignable to parameter of type '{ foo: number; }'.
Property 'foo' is missing in type '{}' but required in type '{ foo: number; }'.
tests/cases/conformance/types/typeRelationships/typeInference/noInfer.ts(42,9): error TS2322: Type 'NoInfer<T>' is not assignable to type 'T'.
'T' could be instantiated with an arbitrary type which could be unrelated to 'NoInfer<T>'.


==== tests/cases/conformance/types/typeRelationships/typeInference/noInfer.ts (7 errors) ====
export declare function foo<T extends string>(a: T, b: NoInfer<T>): void

foo('foo', 'foo') // ok
foo('foo', 'bar') // error
~~~~~
!!! error TS2345: Argument of type '"bar"' is not assignable to parameter of type '"foo"'.

declare class Animal { move(): void }
declare class Dog extends Animal { woof(): void }
declare function doSomething<T>(value: T, getDefault: () => NoInfer<T>): void;

doSomething(new Animal(), () => new Animal()); // ok
doSomething(new Animal(), () => new Dog()); // ok
doSomething(new Dog(), () => new Animal()); // error
~~~~~~~~~~~~
!!! error TS2741: Property 'woof' is missing in type 'Animal' but required in type 'Dog'.
!!! related TS2728 tests/cases/conformance/types/typeRelationships/typeInference/noInfer.ts:7:36: 'woof' is declared here.
!!! related TS6502 tests/cases/conformance/types/typeRelationships/typeInference/noInfer.ts:8:55: The expected type comes from the return type of this signature.

declare function assertEqual<T>(actual: T, expected: NoInfer<T>): boolean;

assertEqual({ x: 1 }, { x: 3 }); // ok
const g = { x: 3, y: 2 };
assertEqual(g, { x: 3 }); // error
~~~~~~~~
!!! error TS2345: Argument of type '{ x: number; }' is not assignable to parameter of type '{ x: number; y: number; }'.
!!! error TS2345: Property 'y' is missing in type '{ x: number; }' but required in type '{ x: number; y: number; }'.
!!! related TS2728 tests/cases/conformance/types/typeRelationships/typeInference/noInfer.ts:17:19: 'y' is declared here.

declare function invoke<T, R>(func: (value: T) => R, value: NoInfer<T>): R;
declare function test(value: { x: number; }): number;

invoke(test, { x: 1, y: 2 }); // error
~~~~
!!! error TS2345: Argument of type '{ x: number; y: number; }' is not assignable to parameter of type '{ x: number; }'.
!!! error TS2345: Object literal may only specify known properties, and 'y' does not exist in type '{ x: number; }'.
test({ x: 1, y: 2 }); // error
~~~~
!!! error TS2345: Argument of type '{ x: number; y: number; }' is not assignable to parameter of type '{ x: number; }'.
!!! error TS2345: Object literal may only specify known properties, and 'y' does not exist in type '{ x: number; }'.


type Component<Props> = { props: Props; };
declare function doWork<Props>(Component: Component<Props>, props: NoInfer<Props>): void;
declare const comp: Component<{ foo: number }>;

doWork(comp, { foo: 42 }); // ok
doWork(comp, {}); // error
~~
!!! error TS2345: Argument of type '{}' is not assignable to parameter of type '{ foo: number; }'.
!!! error TS2345: Property 'foo' is missing in type '{}' but required in type '{ foo: number; }'.
!!! related TS2728 tests/cases/conformance/types/typeRelationships/typeInference/noInfer.ts:29:33: 'foo' is declared here.

declare function mutate<T>(callback: (a: NoInfer<T>, b: number) => T): T;
const mutate1 = mutate((a, b) => b);

declare class ExampleClass<T> {}
class OkClass<T> {
constructor(private clazz: ExampleClass<T>, private _value: NoInfer<T>) {}

get value(): T {
return this._value; // ok
~~~~~~~~~~~~~~~~~~~
!!! error TS2322: Type 'NoInfer<T>' is not assignable to type 'T'.
!!! error TS2322: 'T' could be instantiated with an arbitrary type which could be unrelated to 'NoInfer<T>'.
Andarist marked this conversation as resolved.
Show resolved Hide resolved
}
}


Loading