Typescript generic indexed access type of rest parameters - typescript-generics

I am stuck here:
I have an interface Field which has a name and a value of some unknown type.
interface Field<T> {
name: string;
value: T;
}
I then have a function form, which takes any amount of Field's as rest parameters and returns the data they hold.
function form<T extends Field<unknown>[]>(
...fields: T
): { [k in T[number]['name']]: T[number]['value'] } {
let data = {};
fields.forEach((field) => (data[field.name] = field.value));
return <{ [k in T[number]['name']]: T[number]['value'] }>data;
}
It look like this is action:
const age: Field<number> = { name: 'age', value: 30 };
const sex: Field<string> = { name: 'sex', value: 'men' };
const data = form(age, sex);
// { age: 30, sex: 'men' }
In this example the types of age and sex are just the union of all fields.
data.age; // number | string
data.sex; // number | string
What i want is data to be of type:
const data: { age: number, sex: string } = form(age, sex);
But this returns the error ts(2451).
What does the return type of form need to be.
Is this even possible?
(I'm using typescript version 4.9.3, the latest as of 2022-11-29)
[Edit 2022-11-30]: add Link to working example
typescriptlang.org/play

Your Field should have a ganegic on its name also
interface Field<K extends string, V> {
name: K;
value: V;
}
function ensureField<K extends string, V>(field: Field<K, V>) {
return field;
}
type FieldListToRecord<List extends Field<any, any>[], O extends {} = {}> =
| List extends [infer F extends Field<any, any>, ...infer L extends Field<any, any>[]] ?
FieldListToRecord<L, O & (
F extends Field<infer K, infer V> ? { [k in K]: V }
: never
)>
: { [K in keyof O]: O[K] }; // <- Pure<T>
function form<T extends Field<any, any>[]>(
...fields: T
): FieldListToRecord<T> {
return Object.fromEntries(
fields.map(({ name, value }) => [name, value])
);
}
const age = ensureField({ name: 'age', value: 30 });
// ^?
const sex = ensureField({ name: 'sex', value: 'men' });
// ^?
const data = form(age, sex);
// ^?
// { age: 30, sex: 'men' }
Playground

Related

How can I pattern-match items pulled from a DashMap?

I am trying to pattern match on an enum when getting an item from my dashmap::DashMap. However, it looks like they have a wrapper type over the Entity when they return the data. How can I pattern match over the items then?
use once_cell::sync::Lazy;
use dashmap::DashMap;
enum Entity {
Person { name: String },
Animal { name: String },
}
static ENTITIES: Lazy<DashMap<usize, Entity>> = Lazy::new(|| DashMap::new());
fn main() {
ENTITIES.insert(
0,
Entity::Animal {
name: "pikachu".into(),
},
);
ENTITIES.insert(
1,
Entity::Person {
name: "trainer mike".into(),
},
);
match ENTITIES.get(&0) {
Some(Entity::Animal { name }) => { // compile error here
println!("found animal: {}", name);
}
_ => panic!("did not find person"),
}
}
And the error:
error[E0308]: mismatched types
--> src\lib.rs:__:__
|
| match ENTITIES.get(&0) {
| -------------- this expression has type `Option<dashmap::mapref::one::Ref<'_, usize, Entity>>`
| Some(Entity::Animal { name }) => {
| ^^^^^^^^^^^^^^^^^^^^^^^ expected struct `dashmap::mapref::one::Ref`, found enum `Entity`
|
= note: expected struct `dashmap::mapref::one::Ref<'_, usize, Entity, >`
found enum `Entity`

TypeScript Compiler API: Get resolved return type of a generic method

Supposing we have a generic class or an interface:
interface A<T> {
prop1: T;
func2(): T;
}
interface B extends A<C> {
}
interface C {
}
We need to get the return type of the B.func2 method (not T but C).
The way described here works OK for props, but I can't figure out how to modify it for methods:
for (const statement of sourceFile.statements) {
if (!isInterface(statement))
continue;
if (!statement.heritageClauses?.length)
continue;
for (const heritageClause of statement.heritageClauses) {
for (const exprWithTypeArgs of heritageClause.types) {
const baseType = checker.getTypeAtLocation(exprWithTypeArgs);
for (const propSymbol of baseType.getProperties()) {
const resolvedType = checker.getTypeOfSymbolAtLocation(propSymbol, exprWithTypeArgs);
console.log(`${propSymbol.name} has type: ${resolvedType.symbol?.name}`);
// prints
// prop1 has type: C
// func2 has type: func1
for (const propDeclaration of propSymbol.declarations) {
if (!isSignatureDeclaration(propDeclaration))
continue;
const signature = checker.getSignatureFromDeclaration(propDeclaration);
const returnTypeSymbol = checker.getReturnTypeOfSignature(signature)?.symbol;
const resolvedReturnType = checker.getTypeOfSymbolAtLocation(returnTypeSymbol, exprWithTypeArgs);
console.log(`${propSymbol.name} return type: ${resolvedReturnType.symbol?.name}`);
// prints
// func2 return type: undefined
}
}
}
}
}
What is the correct way of getting resolved return type of a method?
The TypeChecker#getSignaturesOfType method allows for getting the signature of a type.
const bDecl = sourceFile.statements[1]; // obviously, improve this
const bType = typeChecker.getTypeAtLocation(bDecl);
const func2Symbol = bType.getProperty("func2")!;
const func2Type = typeChecker.getTypeOfSymbolAtLocation(func2Symbol, func2Symbol.valueDeclaration);
const func2Signature = checker.getSignaturesOfType(func2Type, ts.SignatureKind.Call)[0];
checker.typeToString(func2Signature.getReturnType()); // C

Flowtype - generic array

How i can write generic function, which take Array of Objects (any type of Object, possible even null and undefined), and filter it to return just valid items of array? If i write it lite this, i will lose genericity :/
// #flow
// Types
type Person = {
id: string,
name: string,
};
type Car = {
id: string,
color: string,
};
// Function definition
const isNotUndefinedOrNull = item => !(item === null || item === undefined);
export const trimList = (list: Array<any> | $ReadOnlyArray<any>): Array<any> => {
return list.filter(isNotUndefinedOrNull);
};
// Constants
const persons = [{ id: 'p1', name: 'Johny' }, null, undefined];
const cars = [{ id: 'c1', color: 'red' }, null, undefined];
// Calls
const trimmedPersons = trimList(persons);
const trimmedCars = trimList(cars);
PROBLEM is, there i have trimmed cars and persons, but flow doesnt know, there is Cars in the trimmedCars list and neither know there is Persons in trimmedPersons list. Flow see just Array and i dont know, how to write is right, to not lose this info.
Flow try
As flow has a bug with Refine array types using filter we use explicit type casting ((res): any): T[]).
function filterNullable<T>(items: (?T)[]): T[] {
const res = items.filter(item => !(item === null || item === undefined);
return ((res): any): T[]);
}
// Example
const a: number[] = filterNullable([1, 2, null, undefined]);
i found it :)
export function trimList<V>(list: Array<?V> | $ReadOnlyArray<?V>): Array<V> {
return R.filter(isNotUndefinedOrNull, list);
}

Property ‘indexOf’ does not exist on type 'FirebaseListObservable<any[]>

I am trying to iterate and find the index of items in a Firebase backend and swipe right and left through these:
export class articleSwiperComponent implements OnInit {
articles: FirebaseListObservable<any[]>;
public orientation: Orientation;
public selectedArticle: article;
private changeDetectorRef: ChangeDetectorRef;
constructor(private af: AngularFire, changeDetectorRef: ChangeDetectorRef) {
this.articles = af.database.list('/articles');
this.changeDetectorRef = changeDetectorRef;
this.orientation = "none";
this.selectedArticle = this.articles[ Math.floor( Math.random() * this.articles.length ) ];
}
I have methods to swipe through the the database articles which will randomly bring forward an article upon first navigating to this page.
public showNextArticle() : void {
this.orientation = "next";
this.changeDetectorRef.detectChanges();
var index = this.articles.indexOf( this.selectedArticle );
this.selectedArticle = this.articles[ index + 1 ]
? this.articles[ index + 1 ]
: this.articles[ 0 ]
;
}
public showPrevarticle() : void {
this.orientation = "prev";
this.changeDetectorRef.detectChanges();
// Find the currently selected index.
var index = this.articles.indexOf( this.selectedArticle );
this.selectedArticle = this.articles[ index - 1 ]
? this.articles[ index - 1 ]
: this.articles[ this.articles.length - 1 ]
;
}
}
However I am getting Property ‘indexOf’ does not exist on type 'FirebaseListObservable<any[]> Property 'length' does not exist on type 'FirebaseListObservable<any[]> errors. What is the equivalent of these properties in Firebase?
I believe your problem is that you're not really working with an array, you're working with an AngularFire Observable (which is effectively an RXJS Observable). This means you would need to subscribe to the observable in order to get the real value of the array, as the observable is used to emit new values of the array as the data changes. See below...
export class articleSwiperComponent implements OnInit, OnDestroy {
public articles: any[] = [];
public orientation: Orientation = 'none';
public selectedArticle: article;
private _articlesSubscription: Subscription;
constructor(private af: AngularFire, private changeDetectorRef: ChangeDetectorRef) {}
ngOnInit() {
// Listen to database
this._articlesSubscription = this.af.database.list('/articles').snapshotChanges().subscribe((snapshot) => {
// Assign articles to local variable as they come in
this.articles = snapshot.map((d) => {
return { $key: d.key, ... d.payload };
});
// Only assign selected article if none assigned
if (!this.selectedArticle) {
this.selectedArticle = this.articles[ Math.floor( Math.random() * this.articles.length ) ];
}
});
}
ngOnDestroy() {
// Destroy listener to database
this._articlesSubscription.unsubscribe();
}
}
If you're not interested in listening to changes to the firebase database you could do this instead:
export class articleSwiperComponent implements OnInit, OnDestroy {
public articles: any[] = [];
public orientation: Orientation = 'none';
public selectedArticle: article;
constructor(private af: AngularFire, private changeDetectorRef: ChangeDetectorRef) {}
ngOnInit() {
// Listen to first emission from database
this.af.database.list('/articles').snapshotChanges().pipe(first()).subscribe((articles) => {
// Assign articles to local variable
this.articles = snapshot.map((d) => {
return { $key: d.key, ... d.payload };
});
this.selectedArticle = this.articles[ Math.floor( Math.random() * this.articles.length ) ];
});
}
}
No matter which option you choose, your showNextArticle() should work as you now have an array object to work from.

How to flowtype cover this code in a function with dereferenced object fields

I'm new to flow, any trying to cover some of my functions, however often I have these snippets where I extract fields form an object based on some condition. But I'm struggling to cover them with flow.
const _join = function ( that: Array<Object>, by: string, index: number) {
that.forEach((thatOBJ: {[string]: any}, i: number)=>{
let obj: {[string]: any} = {};
for (let field: string in thatOBJ) {
if (field !== by) {
obj[`${index.toString()}_${field}`] = thatOBJ[field]; // NOT COVERED
} else {
obj[field] = thatOBJ[field]; // NOT COVERED
}
that[i] = obj;
}
});
}
The array that in this code is a data array so can really be in any format of mongodb data.
Any ideas on what to add to make the two lines which are not covered by flow covered?
Thanks.
A few notes...
This function has a "side effect" since you're mutating that rather than using a transformation and returning a new object.
Array<Object> is an Array of any, bounded by {}. There are no other guarantees.
If you care about modeling this functionality and statically typing them, you need to use unions (or |) to enumerate all the value possibilities.
It's not currently possible to model computed map keys in flow.
This is how I'd re-write your join function:
// #flow
function createIndexObject<T>(obj: { [string]: T }, by: string, index: number): { [string]: T } {
return Object.keys(obj).reduce((newObj, key) => {
if (key !== by) {
newObj[`${index}_${key}`] = newObj[key]
} else {
newObj[key] = obj[key]
}
return newObj
}, {})
}
// NO ERROR
const test1: { [string]: string | number } = createIndexObject({ foo: '', bar: 3 }, 'foo', 1)
// ERROR
const test2: { [string]: string | boolean } = createIndexObject({ foo: '', bar: 3 }, 'foo', 1)

Resources