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

Generic TypeLiteral inference for T extends Array<Array<string>>, which allows type relationship extraction for projects #26841

Closed
4 tasks done
wesleyolis opened this issue Sep 2, 2018 · 2 comments
Labels
Needs More Info The issue still hasn't been fully clarified Suggestion An idea for TypeScript

Comments

@wesleyolis
Copy link

wesleyolis commented Sep 2, 2018

Search Terms

type inference, generics, array literals, array of array, literals, mongoose, relational schema extraction

Suggestion

The typing system can infer simple primitive type literals for function array calls.
I have already managed to get all the underlying plumbing working, the show stopper for this relationship type extract feature for framesworks like mongoose populate and deepPopulate.
The amount of time this relation typing feature would save us is hugh, if type inference could support it.

Repository of current work can be found here
Current nearly working mongoose example
In the file 'index-types-manual-check.ts' a whole bunch of test cases around arrays literal type inference, this feature is requesting support for.

In this example it is clear that support for the request that void and & (intersection) type compound operator support, where void & something mutate into that something as requested in this feature request would be very usefully, as it would allow one to reduce void | x to x. as current extends can't be used with union types. void | string | number extends void | infer A | number ? 'T' : 'F'

[Type merging improvement => x & void = x since y & never = never, instead of u & void = illogical constraint(]#24852)

Overview Examples Uses in Bigger picture

Basic schema

export type Ref<RefId, RefImplem> = {
    RefId : RefId,
    RefImplem : RefImplem
}

export interface LeftSchema {
    a : number,
    Item0: {
        Item1: {
            Item2:Ref<string,number>
        },
        Item11: {     Item12:Ref<'12-A','12-B'>}
        ItemA : { ItemB : Ref<'AB-A', RightSchema>}
        b : boolean,
        c : string,
        d : number
    }
}

export interface RightSchema {
    a : number,
    B : Ref<'12B-A','12B-B'>
}

Populate Relation ship with populate

Mongoose example:

model.findById({}).populate([['ItemA',ItemAB'],['ItemD','ItemDE'],['ItemA','ItemAC']]).exec().then(function(results) {
const value =  results.ItemA.ItemAB;
const value2 = results.ItemD.ItemDE;
const value3 = results.ItemA.ItemAC;
});

Current Schema example


type JoinPaths = [['Item0','e'],['Item0', 'Item1', 'Item2'], ['Item0', 'Item11', 'Item12'],['Item0','ItemA','ItemB','B']];

type SchemaCaseAllJoined = ExtractRelationshipType<LeftSchema, JoinPaths>

// Good all the right keys.
let caseAllJoined : SchemaCaseAllJoined = {
    a : 1,
    Item0 : {
        Item1 : {Item2 : 234},
         Item11 : {Item12 : '12-B'},
         ItemA : {    ItemB : 
        //    'AB-A'
        {a:234,B:'12B-B'},       
        },
        b : true, 
        c : "string",
        d : 234
    }
}

Extraction function

export type ExtractRelationshipType<T extends any, Paths extends {[index:string] : any}, Depth extends string = '0', iterate extends {[index:string] : string} = itemElements> = 
{
    [K in keyof T] :

        KeyInPathsAtDepth<K, T, Paths, Depth> extends void ? 
            T[K] extends Ref<any, any> ? T[K]['RefId'] :
            ExtractRelationshipType<T[K], Paths, iterate[Depth]> 
        : 
    (
    {        
    [Path in keyof ExtractArrayItems<Paths>] :
    
        ObjectHasKey<Paths[Path], Depth> extends 'T' ? 
            Paths[Path][Depth] extends K ? 

                T[K] extends Ref<any, any> ? 
                    ExtractRelationshipType<T[K]['RefImplem'], Paths, iterate[Depth]> :
                    ExtractRelationshipType<T[K], Paths, iterate[Depth]>
            : 
            T[K] extends Ref<any, any> ? void: ExtractRelationshipType<T[K], Paths, iterate[Depth]>            
        : void
    })[keyof ExtractArrayItems<Paths>] 
}

export type KeyInPathsAtDepth<K extends string, T, Paths extends {[index:string]:any}, Depth extends string> =
{
        [Path in keyof ExtractArrayItems<Paths>] :
    
            ObjectHasKey<Paths[Path], Depth> extends 'T' ? 
                Paths[Path][Depth] extends K ? 
                    'T'
                : void
            : void
}[keyof ExtractArrayItems<Paths>]

export type ObjectHasKey<O, L extends string> = StringContains<keyof O, L>;
export type StringContains<S extends string, L extends string> = ({
    [K in S]: 'T';
} & {
    [key: string]: 'F';
})[L];


export type itemElements = {
    '0' : '1'
    '1' : '2'
    '2' : '3'
    '3' : '4'
    '4' : '5'
    '5' : '6'
    '6' : '7'
    '7' : '8'
    '8' : '9'
    '9' : '10'
    '10' : '11'
    '11' : '12'
    '12' : '13'
    '13' : '14'
    '14' : '15'
    '15' : '16'
    '16' : '17'
    '17' : '18'
    '18' : '19'
    '19' : '20'
}

export type ExtractArrayItems<T> = ObjectOmit<T, keyof Array<never>>

export type ObjectOmit<T, K extends string> = {
    [P in 
    
        ({
            [P in keyof T]: P;
        } & {
            [P in K]: never;
        } & {
            [key: string]: never;
        })[keyof T]
    
    ]: T[P];
};

Cases for function array type T generic inference

function basicTypeCapture<T extends string>(value : T) : T
{
    return {} as any
}

const basicTypeCaptureValue = basicTypeCapture('A')


// Not an an array literal capture.
function ArrayTypeCapture<T extends Array<string>>(value : T) : T 
{
    return {} as any
}
// not an array literal capture
// ['ItemA','ItemB']
const arrayTypeCaptureValue = ArrayTypeCapture(['ItemA','ItemB']);

// Good, but problem since can't covert a type into runtime value.
const ArrayTypeCaptureValueGeneric = ArrayTypeCapture<['ItemA','ItemB']>(['ItemA','ItemB']);
ArrayTypeCaptureValueGeneric

// Not an an array literal capture, expect 'T'
function ArrayTypeCaptureEvaluate<T>(value : T) : T extends ['ItemA'] ? 'T' :'F' | T extends Array<'ItemA'> ? 'T' :'F' | T extends {'0' : 'ItemA'} ? 'T' :'F' 
{
    return {} as any
}

// Works, but problem since we can't convert a type into a runtime value
const arrayTypeCaptureEvaluteArrayLiteralsSpecializeGeneric = ArrayTypeCaptureEvaluate<['ItemA']>(['ItemA']);

type EvaluateArrayLiteral<T> = T extends ['ItemA'] ? 'T' :'F' | T extends Array<'ItemA'> ? 'T' :'F' | T extends {'0' : 'ItemA'} ? 'T' :'F' 

// Not an an array literal capture, expect 'T'
function ArrayTypeCaptureEvaluateUsingType<T>(value : T) : EvaluateArrayLiteral<T>
{
    return {} as any
}
// ALso fails.
const arrayTypeCaptureEvaluteArrayLiterals = ArrayTypeCaptureEvaluateUsingType(['ItemA']);

// Works, but it is also a problem since we can't convert a type into a runtime value.
const arrayTypeCaptureEvaluteArrayLiteralsSpecializeGeneric = ArrayTypeCaptureEvaluateUsingType<['ItemA']>(['ItemA']);

// Works
type LeftSchemaPlain = ExtractRefSchema<LeftSchema, ['ItemA']>
const leftSchemaPlain : LeftSchemaPlain;

//
function ExtractRelationShipPath<T,P = never>(schema : T, value : P) : ExtractRefSchema<T,P>
{
    return {} as any
}

const extractRelationShipPath = ExtractRelationShipPath({} as LeftSchema, ['ItemA','Item1','Item2']);
extractRelationShipPath.Item0.Item1.Item2


function CaptureArrayLiteralOfArrayLiteral<P extends Array<Array<string>>>(path : P) : P
{
    return {} as any
}

// Fails
const captureArrayLiteralOfArrayLiteral = CaptureArrayLiteralOfArrayLiteral([['ItemA', 'ItemB']]); 

// Works 
const captureArrayLiteralOfArrayLiteralSpesificGeneric = CaptureArrayLiteralOfArrayLiteral<[['ItemA', 'ItemB']]>([['ItemA', 'ItemB']]); 


function CaptureArrayLiteralOfArrayLiteralAndEvaluate<P extends Array<Array<string>>>(path : P) : P extends Array<['ItemA','ItemB']> ? 'T' : 'F' 
{
    return {} as any
}

// Works
const captureArrayLiteralOfArrayLiteralSpesificGenericA = CaptureArrayLiteralOfArrayLiteralAndEvaluate<[['ItemA', 'ItemB']]>([['ItemA', 'ItemB']]); 

// Fails, which is correctly working as expected.
const captureArrayLiteralOfArrayLiteralSpesificGenericB = CaptureArrayLiteralOfArrayLiteralAndEvaluate<[['ItemA', 'Item']]>([['ItemA', 'Item']]); 



function CapturePath<S extends {}, P extends Array<Array<string>>>(path : P) : ExtractRelationshipType<S, P>
{
    return {} as any
}

const CapturePathCaseA = CapturePath([['']]); // results in never [][], which we need to design our system to handle.
CapturePathCaseA.

Bunch other test cases for referances

// Good only let primary keys.
let caseNoJoins : ExtractRelationshipType<LeftSchema, [never]> = {
    a : 1,
    Item0 : {
        Item1 : {Item2 : '234'},
         Item11 : {Item12 : '12-A'},
         ItemA : {    ItemB : 
            'AB-A'
        },
        b : true, 
        c : "string",
        d : 234
    }
}


// Good only right hand keys, no fails positives.
let caseItem0JoinedNoFalsePositivesA : ExtractRelationshipType<LeftSchema, [['Item0'],['Item0','f'],['Item0','Item11']]> = {
    a : 1,
    Item0 : {
        Item1 : {Item2 : '234'},
         Item11 : {Item12 : '12-A'},
         ItemA : {    ItemB : 
            'AB-A'
        },
        b : true, 
        c : "string",
        d : 234
    }
}

// Good only right hand keys, no fails positives.
let caseItem0JoinedNoFalsePositivesB : ExtractRelationshipType<LeftSchema, [['Item0'],['Item0','f'],['Item0','Item11'],['Item0','Item11','d']]> = {
    a : 1,
    Item0 : {
        Item1 : {Item2 : '234'},
         Item11 : {Item12 : '12-A'},
         ItemA : {    ItemB : 
            'AB-A'
        },
        b : true, 
        c : "string",
        d : 234
    }
}

//Fails item0Item11.Item12 fails, should be left hand key
let caseItem0Item11Item12JoinedNoFalsePositivesB : ExtractRelationshipType<LeftSchema, [['Item0'],['Item0','f'],['Item0','Item11'],['Item0','Item11','d']]> = {
    a : 1,
    Item0 : {
        Item1 : {Item2 : '234'},
         Item11 : {Item12 : '12-B'},
         ItemA : {    ItemB : 
            'AB-A'
        },
        b : true, 
        c : "string",
        d : 234
    }
}

// Good no fail positives.
let caseItem0Item11Item12 : ExtractRelationshipType<LeftSchema, [['Item0'],['Item0','f'],['Item0','Item11'],['Item0','Item11','Item12']]> = {
    a : 1,
    Item0 : {
        Item1 : {Item2 : '234'},
         Item11 : {Item12 : '12-B'},
         ItemA : {    ItemB : 
            'AB-A'
        },
        b : true, 
        c : "string",
        d : 234
    }
}


type JoinPaths = [['Item0','e'],['Item0', 'Item1', 'Item2'], ['Item0', 'Item11', 'Item12'],['Item0','ItemA','ItemB','B']];

type SchemaCaseAllJoined = ExtractRelationshipType<LeftSchema, JoinPaths>

// Good all the right keys.
let caseAllJoined : SchemaCaseAllJoined = {
    a : 1,
    Item0 : {
        Item1 : {Item2 : 234},
         Item11 : {Item12 : '12-B'},
         ItemA : {    ItemB : 
        //    'AB-A'
        {a:234,B:'12B-B'},       
        },
        b : true, 
        c : "string",
        d : 234
    }
}

// Items a should be missign from Item0.ItemA.a
let caseAllJoinedFail : SchemaCaseAllJoined = {
    Item0 : {
        Item1 : {Item2 : true},
        Item11 : {Item12 : '12-A'},
        
        ItemA : {
            ItemB : 'AB-A'
            // Missing the right hand side join
            //{a:234,B:'12B-B'},    
        },
        b : true, 
        c : "string",
        d : 234
    }
}

Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript / JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. new expression-level syntax)
@RyanCavanaugh RyanCavanaugh added Suggestion An idea for TypeScript Needs More Info The issue still hasn't been fully clarified labels Sep 17, 2018
@RyanCavanaugh
Copy link
Member

Is this just #10195 or one of its variants?

@RyanCavanaugh
Copy link
Member

This isn't something we can make progress on

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Needs More Info The issue still hasn't been fully clarified Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

2 participants