Skip to content

JS Flow refactor #556

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

Merged
merged 1 commit into from
Jul 18, 2016
Merged

JS Flow refactor #556

merged 1 commit into from
Jul 18, 2016

Conversation

amadeus
Copy link
Collaborator

@amadeus amadeus commented Jul 18, 2016

This commit rehauls a bunch of the JS Flow features. While still not feature complete, a lot of weird edge cases should now not destroy an entire file, like it used too.

This file should render well, but I would love to add more things that might be broken.

let array: number[] = [1, 2, 3.14, 42];
let theAnswer: number = array[3]; // 42
let offTheEnd: number = array[100]; // No error
let array2: Array<string> = ["an alternate", "syntax", "for arrays"];
let tuple: [string, number, boolean] = ["foo", 0, true];

// Indexing into the array will return the type at a given index.
(tuple[0]: string);
(tuple[1]: string);

// Indexing into an statically unknown index will return a general type.
declare var unknownNumber: number;
// `void` is none of `string`, `number`, or `boolean`
(tuple[unknownNumber]: void);
(tuple[unknownNumber]: string|number|boolean); // OK

// Values written must be compatible with the type at that index.
tuple[1] = -1;
tuple[0] = false;

let object: {foo: string, bar: number} = {foo: "foo", bar: 0};
(object.foo: string);

function durp(): { durp: { what: {} } } {
}

function durp(): [number] {
}

function durp2(): number {
}

function durp3(): <MyClass> {
}

function durp3(): Class<MyClass> {
}


// Property writes must be compatible with the declared type.
object.bar = "bar";

let coolRating: {[id:string]: number} = {};
coolRating["sam"] = 10; // Yes, it's a 0-10 scale.

function makeCallable(): { (x: number): string; foo: number } {
  function callable(number) {
    return number.toFixed(2);
  }
  callable.foo = 123;
  return callable;
}

var callable = makeCallable();

var callableReturn: string = callable(Math.PI); // "3.14"
var callableFoo: number = callable.foo; // 123

var anyObject: Object = {};
anyObject.foo.bar.baz; // OK

function greatestCommonDivisor(a: number, b: number): number {
  if (!b) {
    return a;
  }

  return greatestCommonDivisor(b, a % b);
}

async function getFriendNames(
  friendIDs: Promise<number[]>,
  getFriendName: (id: number) => Promise<string>,
): Promise<string[]> {
  var ids = await friendIDs;
  var names = await Promise.all(ids.map(getFriendName));
  return names;
}

function *infinity(): Generator<number,void,void> {
  var n = 0;
  while (true) {
    yield n++;
  }
}

var anyFunction: Function = () => {};
anyFunction("foo", "bar").baz.quux; // OK

class MyClass {
  foo: string;
  constructor(foo: string) {
    this.foo = foo;
  }
  bar(): string {
    return this.foo;
  }
}

var myInstance: MyClass = new MyClass("foo");
(myInstance.foo: string);
(myInstance.bar(): string);

interface Fooable {
  foo(): string;
}

class AFoo {
  foo() { return "foo from A" };
}

class BFoo {
  foo() { return "foo from B" };
}

(new AFoo: Fooable);
(new BFoo: Fooable);

function DietClass(foo: string) {
  this.foo = foo;
}

DietClass.prototype.bar = function() {
  return this.foo;
}

var myDietInstance: DietClass = new DietClass("foo");
(myDietInstance.foo: string);
(myDietInstance.bar(): string);

var myClass: Class<MyClass> = MyClass;
var myInstance2 = new myClass("foo");

type ObjectWithManyProperties = {
  foo: string,
  bar: number,
  baz: boolean,
  qux: (foo: string, bar: number) => boolean;
}

type GenericObject<T> = { foo: T };
var numberObject: GenericObject<number> = { foo: 0 };
var stringObject: GenericObject<string> = { foo: "foo" };

class GenericClass<T> {
  x: T;
  durp: {
    what: who
  }
  constructor(x: T) {
    this.x = x;
  }
}

var o: ?string = null;
if (o == null) {
  o = 'hello';
}
print(o.length);


class Durp {}

var numberInstance: GenericClass<number> = new GenericClass(0);
var stringInstance: GenericClass<string> = new GenericClass("");

function findMax<T> (arr: T[], compare: (a: T, b: T) => number) {
  var sorted = arr.sort(compare);
  return sorted[sorted.length - 1];
}

// Annotations included for example purposes only.
[1, 2, 3].map((num: number): number => num * 2)


declare type ClassValue = string | {[key: string]: any};

declare module 'classnames' {
  declare function exports(...classes: Array<?ClassValue>): string;
}

declare type ClassValue = string | {[key: string]: any};

declare module 'classnames' {
  declare function exports(...classes: Array<?ClassValue>): string;
}

// You can mixin more than one class
declare class MyClass extends Child mixins MixinA, MixinB {}
declare class MixinA {
  a: number;
  b: number;
}
// Mixing in MixinB will NOT mix in MixinBase
declare class MixinB extends MixinBase {}
declare class MixinBase {
  c: number;
}
declare class Child extends Base {
  a: string;
  c: string;
}
declare class Base {
  b: string;
}

var c = new MyClass();
(c.a: number); // Both Child and MixinA provide `a`, so MixinA wins
(c.b: number); // The same principle holds for `b`, which Child inherits
(c.c: string); // mixins does not copy inherited properties,
               // so `c` comes from Child

declare module 'deep-equal' {
  declare type Options = {
    strict: false
  };

  declare function exports (a: any, b: any, opts: ?Options): boolean;
}

declare var __IOS__: bool;
declare var __ANDROID__: bool;
declare var __MOBILE__: bool;
declare var __WEB__: bool;

declare var process: {
  env: {
    API_ENDPOINT: string,
    API_VERSION: number,
    CDN_HOST: ?string,
    NODE_ENV: 'production' | 'development',
    LOCATION: 'history' | 'hash'
  }
};

declare function requestAnimationFrame(callback: (timestamp: ?number) => void): number;
declare function cancelAnimationFrame(requestID: number): void;

// TODO: these should go somewhere else

declare type Emoji = {
  id: string,
  name: string
};

declare type Source = {
  uri: ?string
};

declare type Application = {
  id: string,
  name: string,
  icon: ?string
}

declare type ConnectionRecord = {
  name: string,
  type: string
}

declare type Style = {[key: string]: string | number}

declare module 'StylusModule' {
}

declare module 'platform' {
  declare class OS {
    architecture: ?string;
    family: ?string;
    version: ?string;
    toString: () => string;
  }

  declare class Platform {
    description: ?string;
    layout: ?string;
    manufacturer: ?string;
    name: ?string;
    prerelease: ?string;
    product: ?string;
    ua: ?string;
    version: ?string;

    os: OS;

    parse: (ua: Object | string) => Platform;
    toString: () => string;
  }

  declare var exports: Platform;

}
declare module Misc {
  declare var DEBUG: bool;
  declare function isLeapYear(year: string): bool;
  declare class Counter {
    val: number;
    incr(): void;
  }
  declare type Response = 'yes' | 'no' | 'maybe';
  declare interface Stack<T> {
    push(item: T): void;
    pop(): T;
    isEmpty(): bool;
  }
}
declare class List<T> {
  map<U>(f: (x: T) => U): List<U>;
}

declare function foo(n: number): string;

function fooList(ns: List<number>): List<string> {
  return ns.map(foo);
}

declare export var className: string;

type Matrix = number[][]; // type of input and output for our function

type Result = Done | Error; // a disjoint union type with two cases
type Done = { status: 'done', answer: Matrix };
type Error = { status: 'error', message: string };

class Foo { }
class Bar { }
// b ends up being a Foo type, since f evaluates to Foo
var b: { f : typeof Foo } = { f : Foo };
// Since the type of b.f is typeof Foo (i.e. Class<Foo>), the following
// assignment is valid because the type of the new instance is Foo:
var inst1: Foo = new b.f();
// However, this fails because the type of the new instance is not Bar:
var inst2: Bar = new b.f();

declare module 'react-native' {
  declare interface IAsyncStorage {
    getItem(
      key: string,
      callback?: ?(error: ?Error, result: ?string) => void
    ): Promise;

    setItem(
      key: string,
      value: string,
      callback?: ?(error: ?Error) => void
    ): Promise;

    removeItem(
      key: string,
      callback?: ?(error: ?Error) => void
    ): Promise;

    clear(
      callback?: ?(error: ?Error) => void
    ): Promise;

    getAllKeys(
      callback?: ?(error: ?Error, keys: ?Array<string>) => void
    ): Promise;

    multiGet(
      keys: Array<string>,
      callback?: ?(errors: ?Array<Error>, result: ?Array<Array<string>>) => void
    ): Promise;
  }

  declare var AsyncStorage: IAsyncStorage;
}

class Box<T> {
  _value: T;

  constructor(value: T) {
    this._value = value;
  }
}

This commit rehauls a bunch of the JS Flow features. While still not
feature complete, a lot of weird edge cases should now not destroy an
entire file, like it used too.
@amadeus amadeus merged commit d745bd0 into develop Jul 18, 2016
@amadeus amadeus deleted the flow-refactor branch July 18, 2016 00:20
@BlooJeans
Copy link

This is really coming together, thanks for your work!

Found a few problems with maybe types

Defining a type that is a function that returns a maybe:

type Authenticator = (str: string) => Promise<?boolean> | ?boolean;

type Accessor<T, V> = (activityItem: T) => ?V;

Defining a generic type with a maybe value

type Loader = DataLoader<string, ?Object>;

type StringArray = Array<?string>;

Also, inner anonymous functions that have return types are broken

function innerFunctionReturnTypes() {
  return ['a','b','c','d'].map((letter: string, index: number): boolean => {
    return index % 2 === 0;
  });
}

@amadeus amadeus mentioned this pull request Jul 20, 2016
@amadeus
Copy link
Collaborator Author

amadeus commented Jul 20, 2016

@BlooJeans I've added some fixes for those cases in #564

@BlooJeans
Copy link

Those changes work great, but unfortunately one more bug...

test0: Correct (but no flow definition)
test1: Defining a function that returns another function w/ arrow function
test2: Defining a function that returns another function w/ regular function

screen shot 2016-07-20 at 16 37 11

function test0(){
  return (ret) => {
    if (ret) {
      console.log('ret'+ret);
    }
    return;
  };
}

function test1(): (ret: any) => void {
  return (ret) => {
    if (ret) {
      console.log('ret'+ret);
    }
    return;
  };
}

function test2(): (ret: any) => void {
  return function(ret) {
    if (ret) {
      console.log('ret'+ret);
    }
    return;
  };
}

Can I buy you a beer via gittip or something? I feel like you're doing all this work just for me

@amadeus
Copy link
Collaborator Author

amadeus commented Jul 21, 2016

Do you mind starting a new issue for this? That way we can keep track of these more openly.

Also, no worries about gittip or whatever. I use Flow on the daily, this stuff is biting my constantly so I need to fix it for myself as well xD

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants