Problem narrowing multiple properties with flow type - flowtype

i'm wondering why the following code isn't valid in flow.
Flowtyped try link
/* #flow */
type Foo = {
bar: string
}
type Data = {
acme: null | { nodes: Foo[] },
beta: null | { nodes: Foo[] }
}
function a(data: Data|null) {
if (
!data ||
!data.acme ||
!data.acme.nodes ||
!data.beta ||
!data.beta.nodes
) {
return
}
const filteredAcme: Foo[] = data.acme.nodes.filter(Boolean);
const filteredBeta: Foo[] = data.beta.nodes.filter(Boolean); // <-- error on this line
// Cannot get `data.beta.nodes` because property `nodes` is missing in null [1].
}
it seems like the if statement should narrow the types so that we know beta.nodes are present.

It should, but current version of flow poorly detect presents checked on the object properties, creating local variables should help. Like the following:
Check in Flow Try
/* #flow */
type Foo = {
bar: string
}
type Data = {
acme: null | { nodes: Foo[] },
beta: null | { nodes: Foo[] }
}
function a(data: Data|null) {
if (!data) return
const {beta, acme} = data;
if (!beta || !beta.nodes || !acme || !acme.nodes) return ;
const filteredAcme: Foo[] = acme.nodes.filter(Boolean);
const filteredBeta: Foo[] = beta.nodes.filter(Boolean); // <-- ???
}
function b(data: Data|null) {
if (!data) return
if (!data.acme) return
if (!data.acme.nodes) return
const filteredAcme: Foo[] = data.acme.nodes.filter(Boolean);
if (!data.beta) return
if (!data.beta.nodes) return
const filteredBeta: Foo[] = data.beta.nodes.filter(Boolean);
}
function c(data: Data|null) {
if (!data) return
if (!data.acme) return
if (!data.acme.nodes) return
const {beta} = data;
if (!beta) return
if (!beta.nodes) return
const filteredAcme: Foo[] = data.acme.nodes.filter(Boolean);
const filteredBeta: Foo[] = beta.nodes.filter(Boolean);
}

Related

Generic parameter 'C' could not be inferred

I have a Data class as follows:
class Data: Hashable, Equatable {
var id: Int = 0
var name: String = ""
var date: Date = Date()
var paymentStatus: Bool = false
static func == (lhs: Data, rhs: Data) -> Bool {
return lhs.id ==
}
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
}
var data1: Data {
let data = Data()
data.id = 1
data.name = "John"
data.date = Calendar.current.date(byAdding: DateComponents(day: -6), to: Date())!
data.paymentStatus = true
return data
}
var data2: Data {
let data = Data()
data.id = 2
data.name = "Peter"
data.date = Date()
data.paymentStatus = false
return data
}
I’m trying to display the data in sections as follows:
struct ContentView: View {
var data: [Data] = [data1, data2]
var body: some View {
List {
ForEach(groupByDate(data), id: \.self) { studentsInMonth in
Section(header:Text(Date(), style: .date)) {
ForEach(data, id:\.self) { item in
HStack {
Text(item.name)
padding()
Text(item.date, style: .time)
if(item.paymentStatus == false) {
Image(systemName: "person.fill.questionmark")
.foregroundColor(Color.red)
} else {
Image(systemName: "banknote")
.foregroundColor(Color.green)
}
}
} // ForEach ends here...
} // section ends here
} // ForEach ends here
} // List ends here
}
}
func groupByDate(_ data: [Data]) -> [Date: [Data]] {
let empty: [Date: [Data]] = [:]
return data.reduce(into: empty) { acc, cur in
let components = Calendar.current.dateComponents([.year, .month], from: cur.date)
let date = Calendar.current.date(from: components)!
let existing = acc[date] ?? []
acc[date] = existing + [cur]
}
}
Not sure what mistake I’m making, but its throwing two errors:
Cannot convert value of type ‘[Date:[Data]]’ to expected argument type
Generic parameter ‘C’ could not be inferred
Appreciate any help
Right now, you're trying to iterate through a Dictionary using ForEach, which doesn't work -- ForEach expects a RandomAccessCollection, which is most often, an Array.
Instead, you can iterate over just the keys of the Dictionary.
struct ContentView: View {
var data: [Data] = [data1, data2]
var body: some View {
let grouped = groupByDate(data)
List {
ForEach(Array(grouped.keys), id: \.self) { key in
let studentsInMonth = grouped[key]!
Section(header:Text(key, style: .date)) {
ForEach(studentsInMonth, id:\.self) { item in
HStack {
Text(item.name)
padding()
Text(item.date, style: .time)
if(item.paymentStatus == false) {
Image(systemName: "person.fill.questionmark")
.foregroundColor(Color.red)
} else {
Image(systemName: "banknote")
.foregroundColor(Color.green)
}
}
} // ForEach ends here...
} // section ends here
} // ForEach ends here
} // List ends here
}
}
Although the above works, I'd consider refactoring your groupByDate function so that it returns an Array directly instead of having to go through the above transformations.
Also, unrelated to your question, but right now, in your header, you're displaying a date -- you get just the year and month, but then display it as a full date, so the header says "January 1, 2022" for all the January dates. You probably want to refactor this to just show the month and year.

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

get only changed objects from observed array with rxjs

I have an array of objects (not primitives) called "streaks" on my state tree and I want to observe only the changes to that array, not simply emit the entire array every time it changes. When I try this using pairwise() I get two identical arrays every time, even though I thought pairwise() would join the previous version and the current version. Why is pairwise() sending two identical arrays? NOTE streaks[1] and streaks[0] are identical, so _.differenceBy() isn't finding any changes because the two arrays are the same.
import {from} from "rxjs";
import {map, pairwise} from "rxjs/operators";
import * as _ from 'lodash';
const state$ = from(store);
const streaks$ = state$.pipe(
map(state => state.streaks),
// distinctUntilChanged(), <-- i've tried this and nothing is output at all
pairwise(),
map(streaks => {
let diff = _.differenceBy(streaks[0], streaks[1], _.isEqual);
console.log('diff', diff); //<-- this is an empty array
return diff;
})
);
streaks$.subscribe((streaksArray) => {
console.log('STREAKS$ 0', streaksArray); //<-- this is never even hit
} );
I solved this issue by creating an distinctUntilChangedArray operator that uses a self written compareArray function
Create compare array function
const compareArray<T> = (first: T[], second: T[], comparator=: (obj: T, obj2: T) => boolean): boolean {
return (first.length === 0 && second.length === 0)
|| (first.length === second.length && first.every((value, index) => {
return comparator
? comparator(value, second[index])
: JSON.stringify(value) === JSON.stringify(second[index]);
}))
}
Maybe you find a better array comparator. This is just my personal implementation of it
Create distinctUntilChangedArray operator
const distinctUntilChangedArray<T> = (comparator?: (prev: T, curr: T) => boolean): MonoTypeOperatorFunction<T[]> {
return distinctUntilChanged((prev: T[], curr: T[]) => {
return compareArray(first, second, comparator);
}
}
Final usage
interface nonPrimitive {
id: number;
name: string;
}
const nonPrimitiveComparator = (prev: nonPrimitive, curr: nonPrimitive): boolean => {
return prev.id === curr.id && prev.name === curr.name;
}
const source$: Observable<nonPrimitive>;
const distinctedSource$ = source$.pipe(
distinctUntilChangedArray(nonPrimitiveComparator)
);

Maybe-type of generic type parameter is incompatible with empty

For every instantiation of RemoteEntity, I get an error on the type parameter that This type is incompatible with empty, referencing the null value for value in newRemoteEntity:
export type RemoteEntity<T: Identifiable> = {
value: ?T;
error: ?Error;
// ...
}
export function newRemoteEntity(): RemoteEntity<*> {
return {
value: null, // error
error: null, // OK
// ...
}
}
If I instead declare value: ?Object, these errors go away (but then I get other errors related to the loss of my type bound). Am I missing something or is this Flowtype bug/quirk?
I found a workaround by making the fields optional (instead of required but with a maybe-type). However, it makes other code a little more complicated (since I have to check for nulls instead of just propagating them in object literals), so I would prefer having maybe-types work.
export type RemoteEntity<T: Identifiable> = {
value?: T;
error?: Error;
pendingAction: ?string;
// ...
}
export function newRemoteEntity(): RemoteEntity<*> {
return {
pendingAction: null,
// ...
}
}
export function requested<T: Identifiable>(
state: RemoteEntity<T>, action: string, id?: string): RemoteEntity<T> {
// Maybe-type version was more concise:
// return {
// state: id && state.value && state.value.id === id ? state.value : null,
// error: null,
// pendingAction: action,
// // ...
// }
const result: RemoteEntity<T> = {
pendingAction: action,
// ...
}
if (id && state.value && state.value.id === id) {
result.value = state.value
}
return result
}

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