What type should I use in typescript to represent any class?
I'm trying to write a function that takes an array of classes and returns an array with different order.
function shuffle(classes: typeof Object[]) : typeof Object[] {
return ...;
}
class A { }
class B extends A { }
class C extends B { }
class D extends B { }
shuffle([A, B, C, D]);
Argument of type 'typeof A[]' is not assignable to parameter of type 'ObjectConstructor[]'.
Then I've tried:
shuffle([typeof A, typeof B, typeof C, typeof D]);
error TS2345: Argument of type 'string[]' is not assignable to parameter of type 'ObjectConstructor[]'.
Type 'string' is not assignable to type 'ObjectConstructor'.
What's the right way? Generics? How? This doesn't work:
export function <T extends typeof Object> shuffle(classes: T[]) : T[]
This neither:
export function <T extends Object> sortClassesBySpeciality(classes: typeof T[]) : typeof T[]
Also why typeof (typeof A) is "string" and "" + typeof A is function? Ok, got this, typeof has two very different meanings context of type definition and expression.
(The ultimate goal is to sort the classes by level of extends from Object.)
You should avoid using the type Object in typescript, you better use any as the docs say:
You might expect Object to play a similar role, as it does in other
languages. But variables of type Object only allow you to assign any
value to them - you can’t call arbitrary methods on them, even ones
that actually exist
But if you want to represent classes then you need to have the following form:
{ new (): CLASS_TYPE }
Or in your case:
function shuffle(classes: Array<{ new (): any }>): Array<{ new (): any }> {
return [];
}
class A { }
class B extends A { }
class C extends B { }
class D extends B { }
shuffle([A, B, C, D]);
(code in playground)
If all of your classes are based on a super class (as your example implies) then you can simply do:
function shuffle(classes: Array<{ new (): A }>): Array<{ new (): A }> {
return [];
}
Edit
Just saw that you want to
sort the classes by level of extends from Object
To answer that:
function shuffle(classes: Array<{ new (): any }>): Array<{ new (): any }> {
return classes.sort((a, b) => getInheritanceLevel(a) - getInheritanceLevel(b));
}
function getInheritanceLevel(cls: { new (): any }): number {
let level = 0;
while (Object.getPrototypeOf(cls.prototype) !== Object.prototype) {
level++;
cls = Object.getPrototypeOf(cls.prototype).constructor;
}
return level;
}
shuffle([D, A, C, B]); // returns [A, B, D, C]
(code in playground)
Related
I have a function which iterates over an object's properties, like this:
function somef(obj) {
for (const prop in obj) {}
}
This is reported as a possible string by Flowtype:
Cannot iterate using a `for...in` statement because string [1] is not an object, null, or undefined. [invalid-in-rhs]
54| for (const prop in obj) {}
^^^
References:
54| for (const prop in obj) {}
^^^^ [1]
I tried typing obj as any, and this checks, but then it allows passing in a string to somef(), which I understand as string being an any:
function somef(obj:any) {
for (const prop in obj) {} // Would fail at runtime
}
somf("mystring) // Checks
Is it possible to restrict somef to only accept bracket objects obj of the form {} on which I can call: for (prop in obj) {} on?
Your problem is that you have not given any explicit type def to the function so it implicitly defines types based on the code around it, given it's being called with a string, it would assume obj is a string and it can't be looped. It throws an error correctly, just not in the place you expect because of this implicitly typed def.
If you know the exact structure of the object, you can type as
function somef(obj: { a: string, b: string }) {
for (const prop in obj) {}
}
But if it's more unknown I recommend indexer object,
function somef(obj: { [key: string]: any }) {
for (const prop in obj) {} // Would fail at runtime
}
docs: https://flow.org/en/docs/types/objects/
try example
I'd like to determine if an array type is readonly. This includes ReadonlyArray and readonly prefixed.
Examples:
type a = ReadonlyArray<string>
type b = readonly string[]
The relevant non-exposed TypeChecker code is:
let globalReadonlyArrayType = <GenericType>getGlobalTypeOrUndefined("ReadonlyArray" as __String, /*arity*/ 1) || globalArrayType;
function isReadonlyArrayType(type: Type): boolean {
return !!(getObjectFlags(type) & ObjectFlags.Reference) && (<TypeReference>type).target === globalReadonlyArrayType;
}
function getGlobalTypeOrUndefined(name: __String, arity = 0): ObjectType | undefined {
const symbol = getGlobalSymbol(name, SymbolFlags.Type, /*diagnostic*/ undefined);
return symbol && <GenericType>getTypeOfGlobalSymbol(symbol, arity);
}
function getTypeOfGlobalSymbol(symbol: Symbol | undefined, arity: number): ObjectType {
function getTypeDeclaration(symbol: Symbol): Declaration | undefined {
const declarations = symbol.declarations;
for (const declaration of declarations) {
switch (declaration.kind) {
case SyntaxKind.ClassDeclaration:
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.EnumDeclaration:
return declaration;
}
}
}
if (!symbol) {
return arity ? emptyGenericType : emptyObjectType;
}
const type = getDeclaredTypeOfSymbol(symbol);
if (!(type.flags & TypeFlags.Object)) {
error(getTypeDeclaration(symbol), Diagnostics.Global_type_0_must_be_a_class_or_interface_type, symbolName(symbol));
return arity ? emptyGenericType : emptyObjectType;
}
if (length((<InterfaceType>type).typeParameters) !== arity) {
error(getTypeDeclaration(symbol), Diagnostics.Global_type_0_must_have_1_type_parameter_s, symbolName(symbol), arity);
return arity ? emptyGenericType : emptyObjectType;
}
return <ObjectType>type;
}
TypeChecker Method
cspotcode pointed out that you can get IndexInfo via the TypeChecker.
const isReadonlyArrayType = (type: Type) =>
type.checker.isArrayLikeType(type) &&
!!type.checker.getIndexInfoOfType(type, IndexKind.Number)?.isReadonly
TS Compiler Method
The following matches the compiler's logic.
let globalReadonlyArrayType: Type;
export const isReadonlyArrayType = (type: Type): boolean => {
const { checker } = type;
if (!globalReadonlyArrayType) {
const symbol =
checker.resolveName('ReadonlyArray', /* location */ void 0, SymbolFlags.Type, /* excludeGlobals */ false)!;
globalReadonlyArrayType = checker.getDeclaredTypeOfSymbol(symbol);
}
return !!((type as ObjectType).objectFlags & ObjectFlags.Reference) &&
((<TypeReference>type).target === globalReadonlyArrayType);
};
Notes
It appears that there may be no immediate advantage of the TypeChecker method over using the Compiler method. The one concern that I had was that comparing target equality may fail if ReadonlyArray was extended, but it appears that this is currently not possible with TypeScript (v3.9.3)
Logic-wise, if performing isArrayLikeType first, the TypeChecker method would be performing a little more work, but likely not enough to worry about in terms of performance.
With that said, it seems that there may be advantage in the TypeChecker method over the second in the event that TS changes its readonly logic, allows extension of ReadonlyArray, etc.
For that reason, I'd recommend using the TypeChecker method.
If you're not using byots, you could probably replace the call to isArrayLikeType with !!((type as ObjectType).objectFlags & ObjectFlags.Reference)
Caveat: My understanding of ReadonlyArray is at a basic level, as of writing this, so if I'm wrong on any of this, please let me know!
In redux, the state should be immutable. I would like Flow to prevent anyone from mutating that state. So, given an object of arbitrary depth:
type object = {
a: {
b: {
d: string
}
},
c: number
}
How can I create a new type that is recursively readonly, so that I cannot do:
let TestFunction = (param: $RecursiveReadOnly<object>) => {
param.a.b.d = 'some string'
}
The builtin $ReadOnly utility of Flow will create a type like this, which isn't what is needed, because b & d are still writable:
{
+a: {
b: {
d: string
}
},
+c: number
}
I've been trying to use the $Call & $ObjMap(i), but I can't figure out how to recursively travel an object in Flow. The objective is to have this:
{
+a: {
+b: {
+d: string
}
},
+c: number
}
Thanks to kalley for his solution. From what I understood, kalley tried to make any object received by a function recursively read only. Since I really only needed known objects as parameters, this works perfectly:
// Type definition that works with arbitrary nested objects/arrays etc.
declare type RecursiveReadOnly<O: Object> = $ReadOnly<$ObjMap<O, typeof makeRecursive>>
declare type RecursiveReadOnlyArray<O: Object> = $ReadOnlyArray<$ReadOnly<$ObjMap<O, typeof makeRecursive>>>
type Recursive<O: Object> = $ObjMap<O, typeof makeRecursive>
declare function makeRecursive<F: Function>(F): F
declare function makeRecursive<A: Object[]>(A): $ReadOnlyArray<$ReadOnly<Recursive<$ElementType<A, number>>>>
declare function makeRecursive<O: Object>(O): RecursiveReadOnly<O>
declare function makeRecursive<I: string[] | boolean[] | number[]>(I): $ReadOnlyArray<$ElementType<I, number>>
declare function makeRecursive<I: string | boolean | number | void | null>(I): I
// Usage example.
type obj = {
a: {
b: {
d: string,
}
}
}
let TestFunction = (param: RecursiveReadOnly<obj>) => {
param.a.b.d = 'some string' // Flow throws an error
}
I'm having trouble figuring out the problem that flow is complaining about. I'm trying to allow the implementation of an API be changeable by storing the implementation class, then later instantiating it, however, flow complains when I call new this.implKlass saying that "Constructor cannot be called on object type". What is flow trying to tell me, and what am I conceptually missing about how flow works?
Example code below, and flow try code here
/* #flow */
type ApiT = {
fnA(): Promise<*>;
}
// An implementation of the API
class Impl {
async fnA(): Promise<*> { return 1; }
}
class DoThings {
implKlass: ApiT;
constructor(klass) {
this.implKlass = klass;
}
callA() {
const Klass = this.implKlass;
const inst = new Klass();
return inst.fnA();
}
}
new DoThings(Impl).callA();
Example output:
18: const inst = new Klass();
^ constructor call. Constructor cannot be called on
18: const inst = new Klass();
^ object type
13: constructor(klass: ApiT) {
^ property `fnA`. Property not found in
23: new DoThings(Impl).callA();
^ statics of Impl
With a small modification this works.
class DoThings {
implKlass: Class<ApiT>;
constructor(klass) {
this.implKlass = klass;
}
callA() {
const Klass = this.implKlass;
const inst = new Klass();
return inst.fnA();
}
}
The bug was you were writing ApiT instead of Class<ApiT>. ApiT would be an instance of a class, while Class<ApiT> is the class itself.
Try flow link
ApiT describes an object type, not a class type. An instance of the Impl class satisfies the ApiT type, but the class Impl itself does not. You cannot call Impl.fnA(), for example.
I'm not sure if there is any way to pass around constructors like this. However you can accomplish basically the same thing by using a factory function:
type ApiT = {
fnA(): Promise<*>;
}
type ApiTFactory = () => ApiT;
class Impl {
async fnA(): Promise<*> { return 1; }
}
class DoThings {
factory: ApiTFactory;
constructor(factory: ApiTFactory) {
this.factory = factory;
}
callA() {
const factory = this.factory;
const inst = factory();
return inst.fnA();
}
}
new DoThings(() => new Impl()).callA();
tryflow link
I have the following function to access a property's delegate. It uses Kotlin reflection to get a property's name and Java reflection to get the field.
fun Any.getDelegate<T>(prop: KProperty<T>): Any {
return javaClass.getDeclaredField("${prop.name}\$delegate").let {
it.setAccessible(true)
it.get(this)
}
}
The method is used like this:
val delegate = a.getDelegate(A::b)
However, I would prefer to use it like this:
val delegate = a.b.delegate
The problem with the code above is getting the property name of a.b and getting the instance a from a.b. From what I know about Kotlin, this is probably not possible, however I'd like to see if I can clean up my function at all.
To give a bigger picture of what I'm trying do here's my complete code. I want an observable delegate to which I can add and remove observers using the delegate reference and without creating addition variables.
fun Any.addObservable<T>(prop: KProperty<T>, observer: (T) -> Unit) {
getObservableProperty(prop).observers.add(observer)
}
fun Any.getObservableProperty<T>(prop: KProperty<T>): ObservableProperty<T> {
return getDelegate(prop) as ObservableProperty<T>
}
fun Any.getDelegate<T>(prop: KProperty<T>): Any {
return javaClass.getDeclaredField("${prop.name}\$delegate").let {
it.setAccessible(true)
it.get(this)
}
}
class ObservableProperty<T>(
initialValue: T,
initialObservers: Array<(T) -> Unit> = emptyArray()) : ReadWriteProperty<Any?, T> {
private var value = initialValue
public val observers: MutableSet<(T) -> Unit> = initialObservers.toHashSet()
public override fun get(thisRef: Any?, desc: PropertyMetadata): T {
return value
}
public override fun set(thisRef: Any?, desc: PropertyMetadata, value: T) {
this.value = value
observers.forEach { it(value) }
}
}
class A() {
var b by ObservableProperty(0)
}
fun main(args: Array<String>) {
val a = A()
a.addObservable(A::b) {
println("b is now $it")
}
a.b = 1
a.b = 2
a.b = 3
}
Edit:
I just realized that the function also isn't strict because the property delegate field name is referenced by KProperty name, which doesn't require a strong reference to the enclosing class. Here's an example to demonstrate the problem:
class A() {
var foo by ObservableProperty(0)
}
class B() {
var foo by ObservableProperty(0)
}
fun main(args: Array<String>) {
val a = A()
a.addObservable(B::foo) {
println("b is now $it")
}
a.foo = 1
a.foo = 2
a.foo = 3
}
This compiles and runs without error because A::foo and B::foo both result in a field string of "foo$delegate.
Right now reflection is all we can do to get to the delegate object. We are designing a language feature to have direct access to delegate instance, but it's long way to go.
This is how you get the name of a Kotlin Property (although only with an instance of the class). This part will be useful to anyone arriving at this question purely based off its title.
class Stuff(val thing: String)
val stuff = Stuff("cool stuff")
val thingFieldName = "${stuff.thing}\$delegate"
// value of thingFieldName is now "thing"
In terms of getting the delegate itself easier, they say you can now do this:
class Foo {
var bar: String by ReactiveProperty<String>()
}
val foo = Foo()
val bar = foo.bar
val barDelegate = ... // foo.bar$delegate
See ticket.