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

interface extends Record<any, boolean | string | number> value type constraints returns false for aggregate/generic key or index signature #45409

Closed
wesleyolis opened this issue Aug 11, 2021 · 3 comments
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@wesleyolis
Copy link

Bug Report

Well basically, when a generic aggregate type representation is used for a key like string | any in an index signature or Record<aggregate type, specific type constraint>, then typescript fails to validate all keys against the value type constraints. Looks like, that when there are no keys to iterate, that typescript doesn't iterate all the keys available and ensure that they extends the value type constraints.

Typically understanding of any is that it turns off type checking, which is great wanted behavior, however, have tried this with string as well.

🔎 Search Terms

extends record string key
record value type constraints

🕗 Version & Regression Information

Seem to be all version from 3.3 to the latest in the play ground.
This is also the 'Nightly' version in the playground: http://www.typescriptlang.org/play/?ts=Nightly

💻 Code

interface testA {
    fieldA: number
}

interface Extend {
    [index:string]: number | string | boolean
}

// TResult is 'N', expected to be 'Y'
type TResult = testA extends Record<any, string | boolean | number> ? 'Y' :' N'

// TResult1_1 is 'N', expected to be 'Y'
type TResult1_1 = testA extends Record<string, string | boolean | number> ? 'Y' :' N'

// TResult2 is 'N', expected to be 'Y'
type TResult2 = testA extends Extend  ? 'Y' :'N'

enum EnumTest{
    EnumA = 0,
    EnumB = 1,
    EnumC = 2
}

interface testB<T extends EnumTest = EnumTest.EnumA> {
    kind: T 
}
// TResult3 is 'N', expected to be 'Y'
type TResult3 = testB extends Record<any, string | boolean | number> ? 'Y' :' N'

// TResult3_1 is 'N', expected to be 'Y'
type TResult3_1 = testB extends Record<string, string | boolean | number> ? 'Y' :' N'

// TResult4 is 'N', expected to be 'Y'
type TResult4 = testB extends Extend  ? 'Y' :'N'

// TResult5 is 'N', expected to be 'Y', but as far as I am where there is no ability to check for enum
type TResult5 = testB extends Record<any, string | boolean | enum> ? 'Y' :' N'
@MartinJohns
Copy link
Contributor

Working playground link

@MartinJohns
Copy link
Contributor

This is working as intended. See #42825 and others. If you use type instead of interface you get the behaviour you expect.

@wesleyolis
Copy link
Author

@MartinJohns Hi,

Alright, however, I then have a problem validating the following structure forms, because things just don't fit to gather in more complicated problem. So basically then one has to write the following code like blow to make things work, any other ideas on how to make this look well pretty or nice like it would with interfaces.

Could we possibly not improve this by allowing 'type' to proceed an interface, in which will be treated like a concrete type, such things like more legitimate and not like hacks.
i.e

type interface Base<T extends string> {
kind : T,
BaseField: string,
BaseField: number
}

type interface XDeriveBase<T extends string> extends Base<T> {
fieldXDerive: string
}

How I would have normally written such definitions and structures.

enum ROSpecEventParam {
    StartOfRoSpec = 0,
    EndOfRoSpec = 1,
    PreEmptionOfRoSpec = 2
}

interface ROSpecEvent<T extends ROSpecEventParam = ROSpecEventParam> {
    //EventType: T,
    ROSpecID: number     
}

interface ROSpecEventStart extends ROSpecEvent<ROSpecEventParam.StartOfRoSpec> {    
}

interface ROSpecEventEnd extends ROSpecEvent<ROSpecEventParam.EndOfRoSpec> {    
}


interface ROSpecEventPreEmpting extends ROSpecEvent<ROSpecEventParam.PreEmptionOfRoSpec>{    
    PreEmptyingROSpecID: number
}

type ROSpecEvents = ROSpecEventPreEmpting | ROSpecEventStart | ROSpecEventEnd;

type PrimativesTypes = boolean | number | string | Date | null

type PrimativesTypesInContainers2 = PrimativesTypes | PrimativesTypes[]

type RecordsOfPrimatives2<TDecodedInstanceKey extends string> = {
    [K in TDecodedInstanceKey]?: PrimativesTypes
}

type RecordOfPrimatives<TDecodedInstanceKey extends string> =  RecordsOfPrimatives2<TDecodedInstanceKey>

function SomeFunction2<T extends RecordsOfPrimatives2<any>>(obj: T) : T {

    return {} as T;
}

const obj2 = {} as  ROSpecEvents

const res2 = SomeFunction2(obj2);//Invalidate parameter because I am using interface, which can't extend a record.

How I am required to know write them when being used as type as validators, is there not a better syntax we can devise for this.

enum ROSpecEventParam {
    StartOfRoSpec = 0,
    EndOfRoSpec = 1,
    PreEmptionOfRoSpec = 2
}

type ROSpecEvent<T extends ROSpecEventParam = ROSpecEventParam> = {
    EventType: T,
    ROSpecID: number     
}

type ROSpecEventStart = ROSpecEvent<ROSpecEventParam.StartOfRoSpec> & {    
}

type ROSpecEventEnd = ROSpecEvent<ROSpecEventParam.EndOfRoSpec> & {    
}


type ROSpecEventPreEmpting = ROSpecEvent<ROSpecEventParam.PreEmptionOfRoSpec> & {    
    PreEmptyingROSpecID: number
}

type ROSpecEvents = ROSpecEventPreEmpting | ROSpecEventStart | ROSpecEventEnd;

type PrimativesTypes = boolean | number | string | Date | null

type PrimativesTypesInContainers2 = PrimativesTypes | PrimativesTypes[]

type RecordsOfPrimatives2<TDecodedInstanceKey extends string> = {
    [K in TDecodedInstanceKey]?: PrimativesTypes
}

type RecordOfPrimatives<TDecodedInstanceKey extends string> =  RecordsOfPrimatives2<TDecodedInstanceKey>

function SomeFunction<T extends RecordsOfPrimatives2<any>>(obj: T) : T {

    return {} as T;
}

const obj = {} as  ROSpecEvents

const res = SomeFunction(obj);// Validate parameter type check by generic.

@RyanCavanaugh RyanCavanaugh added the Working as Intended The behavior described is the intended behavior; this is not a bug label Aug 11, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests

3 participants