mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-07-06 06:49:29 -05:00
Constraints for mapped types with filtering 'as' clauses (#48699)
* Constraints for mapped types with filtering 'as' clauses * Add regression test
This commit is contained in:
@@ -15737,11 +15737,14 @@ namespace ts {
|
||||
return type[cache] = elementType;
|
||||
}
|
||||
}
|
||||
// If the object type is a mapped type { [P in K]: E }, where K is generic, instantiate E using a mapper
|
||||
// that substitutes the index type for P. For example, for an index access { [P in K]: Box<T[P]> }[X], we
|
||||
// construct the type Box<T[X]>.
|
||||
if (isGenericMappedType(objectType) && !objectType.declaration.nameType) {
|
||||
return type[cache] = mapType(substituteIndexedMappedType(objectType, type.indexType), t => getSimplifiedType(t, writing));
|
||||
// If the object type is a mapped type { [P in K]: E }, where K is generic, or { [P in K as N]: E }, where
|
||||
// K is generic and N is assignable to P, instantiate E using a mapper that substitutes the index type for P.
|
||||
// For example, for an index access { [P in K]: Box<T[P]> }[X], we construct the type Box<T[X]>.
|
||||
if (isGenericMappedType(objectType)) {
|
||||
const nameType = getNameTypeFromMappedType(objectType);
|
||||
if (!nameType || isTypeAssignableTo(nameType, getTypeParameterFromMappedType(objectType))) {
|
||||
return type[cache] = mapType(substituteIndexedMappedType(objectType, type.indexType), t => getSimplifiedType(t, writing));
|
||||
}
|
||||
}
|
||||
return type[cache] = type;
|
||||
}
|
||||
|
||||
@@ -41,4 +41,26 @@ tests/cases/conformance/types/mapped/mappedTypeConstraints2.ts(25,57): error TS2
|
||||
~~~~~~~~~~~~~~
|
||||
!!! error TS2322: Type 'Foo<T>[`get${T}`]' is not assignable to type 'T'.
|
||||
!!! error TS2322: 'T' could be instantiated with an arbitrary type which could be unrelated to 'Foo<T>[`get${T}`]'.
|
||||
|
||||
// Repro from #48626
|
||||
|
||||
interface Bounds {
|
||||
min: number;
|
||||
max: number;
|
||||
}
|
||||
|
||||
type NumericBoundsOf<T> = {
|
||||
[K in keyof T as T[K] extends number | undefined ? K : never]: Bounds;
|
||||
}
|
||||
|
||||
function validate<T extends object>(obj: T, bounds: NumericBoundsOf<T>) {
|
||||
for (const [key, val] of Object.entries(obj)) {
|
||||
const boundsForKey = bounds[key as keyof NumericBoundsOf<T>];
|
||||
if (boundsForKey) {
|
||||
const { min, max } = boundsForKey;
|
||||
if (min > val || max < val) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -24,20 +24,53 @@ type Foo<T extends string> = {
|
||||
};
|
||||
|
||||
const get = <T extends string>(t: T, foo: Foo<T>): T => foo[`get${t}`]; // Type 'Foo<T>[`get${T}`]' is not assignable to type 'T'
|
||||
|
||||
// Repro from #48626
|
||||
|
||||
interface Bounds {
|
||||
min: number;
|
||||
max: number;
|
||||
}
|
||||
|
||||
type NumericBoundsOf<T> = {
|
||||
[K in keyof T as T[K] extends number | undefined ? K : never]: Bounds;
|
||||
}
|
||||
|
||||
function validate<T extends object>(obj: T, bounds: NumericBoundsOf<T>) {
|
||||
for (const [key, val] of Object.entries(obj)) {
|
||||
const boundsForKey = bounds[key as keyof NumericBoundsOf<T>];
|
||||
if (boundsForKey) {
|
||||
const { min, max } = boundsForKey;
|
||||
if (min > val || max < val) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//// [mappedTypeConstraints2.js]
|
||||
"use strict";
|
||||
function f1(obj, key) {
|
||||
var x = obj[key];
|
||||
const x = obj[key];
|
||||
}
|
||||
function f2(obj, key) {
|
||||
var x = obj[key]; // Error
|
||||
const x = obj[key]; // Error
|
||||
}
|
||||
function f3(obj, key) {
|
||||
var x = obj[key]; // Error
|
||||
const x = obj[key]; // Error
|
||||
}
|
||||
const get = (t, foo) => foo[`get${t}`]; // Type 'Foo<T>[`get${T}`]' is not assignable to type 'T'
|
||||
function validate(obj, bounds) {
|
||||
for (const [key, val] of Object.entries(obj)) {
|
||||
const boundsForKey = bounds[key];
|
||||
if (boundsForKey) {
|
||||
const { min, max } = boundsForKey;
|
||||
if (min > val || max < val)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
var get = function (t, foo) { return foo["get".concat(t)]; }; // Type 'Foo<T>[`get${T}`]' is not assignable to type 'T'
|
||||
|
||||
|
||||
//// [mappedTypeConstraints2.d.ts]
|
||||
@@ -63,3 +96,11 @@ declare type Foo<T extends string> = {
|
||||
[RemappedT in T as `get${RemappedT}`]: RemappedT;
|
||||
};
|
||||
declare const get: <T extends string>(t: T, foo: Foo<T>) => T;
|
||||
interface Bounds {
|
||||
min: number;
|
||||
max: number;
|
||||
}
|
||||
declare type NumericBoundsOf<T> = {
|
||||
[K in keyof T as T[K] extends number | undefined ? K : never]: Bounds;
|
||||
};
|
||||
declare function validate<T extends object>(obj: T, bounds: NumericBoundsOf<T>): boolean;
|
||||
|
||||
@@ -104,3 +104,70 @@ const get = <T extends string>(t: T, foo: Foo<T>): T => foo[`get${t}`]; // Type
|
||||
>foo : Symbol(foo, Decl(mappedTypeConstraints2.ts, 24, 36))
|
||||
>t : Symbol(t, Decl(mappedTypeConstraints2.ts, 24, 31))
|
||||
|
||||
// Repro from #48626
|
||||
|
||||
interface Bounds {
|
||||
>Bounds : Symbol(Bounds, Decl(mappedTypeConstraints2.ts, 24, 71))
|
||||
|
||||
min: number;
|
||||
>min : Symbol(Bounds.min, Decl(mappedTypeConstraints2.ts, 28, 18))
|
||||
|
||||
max: number;
|
||||
>max : Symbol(Bounds.max, Decl(mappedTypeConstraints2.ts, 29, 16))
|
||||
}
|
||||
|
||||
type NumericBoundsOf<T> = {
|
||||
>NumericBoundsOf : Symbol(NumericBoundsOf, Decl(mappedTypeConstraints2.ts, 31, 1))
|
||||
>T : Symbol(T, Decl(mappedTypeConstraints2.ts, 33, 21))
|
||||
|
||||
[K in keyof T as T[K] extends number | undefined ? K : never]: Bounds;
|
||||
>K : Symbol(K, Decl(mappedTypeConstraints2.ts, 34, 5))
|
||||
>T : Symbol(T, Decl(mappedTypeConstraints2.ts, 33, 21))
|
||||
>T : Symbol(T, Decl(mappedTypeConstraints2.ts, 33, 21))
|
||||
>K : Symbol(K, Decl(mappedTypeConstraints2.ts, 34, 5))
|
||||
>K : Symbol(K, Decl(mappedTypeConstraints2.ts, 34, 5))
|
||||
>Bounds : Symbol(Bounds, Decl(mappedTypeConstraints2.ts, 24, 71))
|
||||
}
|
||||
|
||||
function validate<T extends object>(obj: T, bounds: NumericBoundsOf<T>) {
|
||||
>validate : Symbol(validate, Decl(mappedTypeConstraints2.ts, 35, 1))
|
||||
>T : Symbol(T, Decl(mappedTypeConstraints2.ts, 37, 18))
|
||||
>obj : Symbol(obj, Decl(mappedTypeConstraints2.ts, 37, 36))
|
||||
>T : Symbol(T, Decl(mappedTypeConstraints2.ts, 37, 18))
|
||||
>bounds : Symbol(bounds, Decl(mappedTypeConstraints2.ts, 37, 43))
|
||||
>NumericBoundsOf : Symbol(NumericBoundsOf, Decl(mappedTypeConstraints2.ts, 31, 1))
|
||||
>T : Symbol(T, Decl(mappedTypeConstraints2.ts, 37, 18))
|
||||
|
||||
for (const [key, val] of Object.entries(obj)) {
|
||||
>key : Symbol(key, Decl(mappedTypeConstraints2.ts, 38, 16))
|
||||
>val : Symbol(val, Decl(mappedTypeConstraints2.ts, 38, 20))
|
||||
>Object.entries : Symbol(ObjectConstructor.entries, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --))
|
||||
>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
|
||||
>entries : Symbol(ObjectConstructor.entries, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --))
|
||||
>obj : Symbol(obj, Decl(mappedTypeConstraints2.ts, 37, 36))
|
||||
|
||||
const boundsForKey = bounds[key as keyof NumericBoundsOf<T>];
|
||||
>boundsForKey : Symbol(boundsForKey, Decl(mappedTypeConstraints2.ts, 39, 13))
|
||||
>bounds : Symbol(bounds, Decl(mappedTypeConstraints2.ts, 37, 43))
|
||||
>key : Symbol(key, Decl(mappedTypeConstraints2.ts, 38, 16))
|
||||
>NumericBoundsOf : Symbol(NumericBoundsOf, Decl(mappedTypeConstraints2.ts, 31, 1))
|
||||
>T : Symbol(T, Decl(mappedTypeConstraints2.ts, 37, 18))
|
||||
|
||||
if (boundsForKey) {
|
||||
>boundsForKey : Symbol(boundsForKey, Decl(mappedTypeConstraints2.ts, 39, 13))
|
||||
|
||||
const { min, max } = boundsForKey;
|
||||
>min : Symbol(min, Decl(mappedTypeConstraints2.ts, 41, 19))
|
||||
>max : Symbol(max, Decl(mappedTypeConstraints2.ts, 41, 24))
|
||||
>boundsForKey : Symbol(boundsForKey, Decl(mappedTypeConstraints2.ts, 39, 13))
|
||||
|
||||
if (min > val || max < val) return false;
|
||||
>min : Symbol(min, Decl(mappedTypeConstraints2.ts, 41, 19))
|
||||
>val : Symbol(val, Decl(mappedTypeConstraints2.ts, 38, 20))
|
||||
>max : Symbol(max, Decl(mappedTypeConstraints2.ts, 41, 24))
|
||||
>val : Symbol(val, Decl(mappedTypeConstraints2.ts, 38, 20))
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -68,3 +68,63 @@ const get = <T extends string>(t: T, foo: Foo<T>): T => foo[`get${t}`]; // Type
|
||||
>`get${t}` : `get${T}`
|
||||
>t : T
|
||||
|
||||
// Repro from #48626
|
||||
|
||||
interface Bounds {
|
||||
min: number;
|
||||
>min : number
|
||||
|
||||
max: number;
|
||||
>max : number
|
||||
}
|
||||
|
||||
type NumericBoundsOf<T> = {
|
||||
>NumericBoundsOf : NumericBoundsOf<T>
|
||||
|
||||
[K in keyof T as T[K] extends number | undefined ? K : never]: Bounds;
|
||||
}
|
||||
|
||||
function validate<T extends object>(obj: T, bounds: NumericBoundsOf<T>) {
|
||||
>validate : <T extends object>(obj: T, bounds: NumericBoundsOf<T>) => boolean
|
||||
>obj : T
|
||||
>bounds : NumericBoundsOf<T>
|
||||
|
||||
for (const [key, val] of Object.entries(obj)) {
|
||||
>key : string
|
||||
>val : any
|
||||
>Object.entries(obj) : [string, any][]
|
||||
>Object.entries : { <T>(o: { [s: string]: T; } | ArrayLike<T>): [string, T][]; (o: {}): [string, any][]; }
|
||||
>Object : ObjectConstructor
|
||||
>entries : { <T>(o: { [s: string]: T; } | ArrayLike<T>): [string, T][]; (o: {}): [string, any][]; }
|
||||
>obj : T
|
||||
|
||||
const boundsForKey = bounds[key as keyof NumericBoundsOf<T>];
|
||||
>boundsForKey : NumericBoundsOf<T>[keyof NumericBoundsOf<T>]
|
||||
>bounds[key as keyof NumericBoundsOf<T>] : NumericBoundsOf<T>[keyof NumericBoundsOf<T>]
|
||||
>bounds : NumericBoundsOf<T>
|
||||
>key as keyof NumericBoundsOf<T> : keyof NumericBoundsOf<T>
|
||||
>key : string
|
||||
|
||||
if (boundsForKey) {
|
||||
>boundsForKey : NumericBoundsOf<T>[keyof NumericBoundsOf<T>]
|
||||
|
||||
const { min, max } = boundsForKey;
|
||||
>min : number
|
||||
>max : number
|
||||
>boundsForKey : NumericBoundsOf<T>[keyof NumericBoundsOf<T>]
|
||||
|
||||
if (min > val || max < val) return false;
|
||||
>min > val || max < val : boolean
|
||||
>min > val : boolean
|
||||
>min : number
|
||||
>val : any
|
||||
>max < val : boolean
|
||||
>max : number
|
||||
>val : any
|
||||
>false : false
|
||||
}
|
||||
}
|
||||
return true;
|
||||
>true : true
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// @strict: true
|
||||
// @declaration: true
|
||||
// @target: es2017
|
||||
|
||||
type Mapped1<K extends string> = { [P in K]: { a: P } };
|
||||
|
||||
@@ -26,3 +27,25 @@ type Foo<T extends string> = {
|
||||
};
|
||||
|
||||
const get = <T extends string>(t: T, foo: Foo<T>): T => foo[`get${t}`]; // Type 'Foo<T>[`get${T}`]' is not assignable to type 'T'
|
||||
|
||||
// Repro from #48626
|
||||
|
||||
interface Bounds {
|
||||
min: number;
|
||||
max: number;
|
||||
}
|
||||
|
||||
type NumericBoundsOf<T> = {
|
||||
[K in keyof T as T[K] extends number | undefined ? K : never]: Bounds;
|
||||
}
|
||||
|
||||
function validate<T extends object>(obj: T, bounds: NumericBoundsOf<T>) {
|
||||
for (const [key, val] of Object.entries(obj)) {
|
||||
const boundsForKey = bounds[key as keyof NumericBoundsOf<T>];
|
||||
if (boundsForKey) {
|
||||
const { min, max } = boundsForKey;
|
||||
if (min > val || max < val) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user