Flow: Interface declarations and other exotic syntax

Created on 29 May 2015  路  12Comments  路  Source: facebook/flow

Could someone comment on the current status and maturity of the following annotation language constructs, which don't seem to be documented?

  1. interface, implements, and interface (single|multiple) inheritance - As in lib/core.js and elsewhere.
  2. Indexed properties? (e.g. [index: number]: File)
  3. (Static) Function call operator overloading? (e.g. static (...values:Array<any>): Array<any>)

I am working on a WebIDL to Flow converter motivated by #477 (with some relevant discussion in #165 and #140). The basics work, but for a robust conversion I could really use some pointers as to the semantics of the above (and whether it's wise to rely on them at all).

For example, am I correct in assuming that an interface, like type and unlike class, does not result in its identifier added as a property on the global scope? This interpretation is analogous to the effect of [NoInterfaceObject] on a WebIDL interface.

question

All 12 comments

Cool! I'm excited to see the result of this work.

You are correct that interfaces are "compiled away" and don't result in any runtime bindings on their own.

implements is not implemented currently (no pun intended). I'm not sure if anyone is working on it. Can you share some IDL that you would like to translate using it? Maybe we can find alternatives, or possibly try to get it working.

Indexed properties might have some issues surrounding them, but I think it's safe to use them assuming they work properly. Any bugs there we should fix, but I don't think it should block what you're working on.

What do you mean exactly by operator overloading as it relates to static function calls? Overloading in flow doesn't quite work as you might expect coming from TypeScript, but I don't think anyone has spent a lot of time working out the details there. Again, an example of what you might like to generate given IDL would help here.

Overall, I'd recommend that you not let the maturity of features in flow block you from getting this important work done. If anything, your efforts will inspire us to get these things in working order! Thanks!

Thanks for the encouragement, it means a lot :)


Re: Interfaces being "compiled away" - so is this a bug?

interface I {}
type T = {};
new I();    // <--- No error!
new T();    // Identifier T: could not resolve name

It seems to me that implements (and multiple inheritance for interface?) would best represent WebIDL's A implements B; statement (which is defined to mean "all objects that implement A must also implement B"). I don't have data on how often this would come up when translating real-world IDL (but I think it will).

My current workaround is to walk the inheritance/implements DAG and actually copy members from A to B and so on. This also provides a solution for name hiding in the [NoInterfaceObject] case - just emit the respective interface as type rather than class and remove all inheritance/implements references to it, as they're now redundant. I _think_ (more thoughts welcome) that this emulates the WebIDL ECMAScript binding correctly. But the resulting Flow declarations are more verbose and less semantic than they probably could be.


By overloading of the function call operator I mean declarations that make objects callable, such as

declare module "assert" {
  declare var exports: {
    (value: any, message?: string): void;
  }
}

Which seems to fit legacycaller.


More questions:
1) What _about_ overloading? WebIDL supports it for functions, constructors, etc. Does Flow? Or should I calculate the union signatures myself?
2) WebIDL also has _named constructors_, as in:

[NamedConstructor=Audio(optional DOMString src)]
interface HTMLAudioElement : HTMLMediaElement {};

This defines (in addition to any constructor which HTMLAudioElement may or may not expose under its own name) a global constructor named Audio which constructs HTMLAudioElements.
But how do I represent it in Flow? The following seems inadequate.

class HTMLAudioElement {}
declare function Audio(src?: string): HTMLAudioElement;
//...
var a: HTMLAudioElement = new Audio(); // <---- Error

I think you're right about the interface bug. I opened a PR to fix at #481.

I think your solution of copying through the DAG is a great workaround. As long as you also declare the interfaces themselves, so consumers can use them as types. I'll look into implements in the mean time.

Object types can have callable properties, but I'm having trouble defining one on an interface, so I'm not sure that we can support legacycaller for class types right now. Could be very wrong about this.

I'm also not quite sure what level of overloading support flow has right now, but afaik we want to support it. It would be very helpful if you could generate overloaded signatures and file issues for any unexpected behavior that results.

Named constructors do expose an invalid assumption flow makes about constructors. Flow assumes that constructors return void and return objects of the class type. This doesn't always hold: explicit return from the constructor` and this, too. You're also right that just declaring a function won't work, because it needs to be used as a constructor, and constructors can't currently have a different return type.

@popham, you know a lot more about class semantics than I do. Any thoughts about this?

Just wanted to point you towards the repo where I'm working on the converter: motiz88/webidl-to-flow
It's neither feature-complete nor tested nor documented, really, but I may have to take a break from developing it for a few days so I opted to push _something_ in the mean time.

I recall seeing that interface is deprecated in source comments. I'd avoid it like plague. (I vaguely remember seeing that the interface concept was split to type + declare class.)

Whoops. Looks like it hasn't been deprecated yet. parser/parser_flow.ml:

(* TODO: deprecate `interface`; somewhat confusingly, it plays two roles,
       which are subsumed by `type` and `declare class` *)

I'm looking for a way to do declare class A extends B implements C, D, E, where B, C, D, E may or may not originate as [NoInterfaceObject] types (=> Flow type, or interface if that stays). For now I will keep emulating this outside of Flow.

On a related note, Flow happily _parses_ multiple types in extends, but immediately discards all but the first. Maybe it should warn/error instead?

declare class B1 {prop1: string}
declare class B2 {prop2: string}
type T = {prop3: string}
declare class D extends B1, B2, T {} // <--- No error - cf. `extends T`
var b1: B1 = new D(); // <--- No type error
var b2: B2 = new D(); // <--- Type error
var t: T = new D(); // <--- prop3 not found in D

Just a status update here. I'm working on full interface support, which will basically do the following:

  • interface can extend multiple interfaces
  • declare class can extend one class
  • no "implements" for now, since instances of classes will be structurally subtyped with interfaces, whether or not you declare them. In the future, we could support "implements" for better doc / perf etc.
  • many declare classes in the libs will be converted to interfaces, thus allowing structural subtyping
  • declare classes will continue to be nominally subtyped with other classes, but may act like interfaces when compared to object types.

Wanted to just mention that interface and implements support has landed.

@nmn uh, where exactly? Any doc for them?

Here are the docs for interfaces:
https://flowtype.org/en/docs/types/interfaces/

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jamiebuilds picture jamiebuilds  路  3Comments

mjj2000 picture mjj2000  路  3Comments

ghost picture ghost  路  3Comments

davidpelaez picture davidpelaez  路  3Comments

marcelbeumer picture marcelbeumer  路  3Comments