Skip to content
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

Refinement with instanceof and generic unions is unsound #6741

Open
wchargin opened this issue Aug 14, 2018 · 5 comments
Open

Refinement with instanceof and generic unions is unsound #6741

wchargin opened this issue Aug 14, 2018 · 5 comments
Assignees
Labels
bug Typing: soundness

Comments

@wchargin
Copy link
Contributor

@wchargin wchargin commented Aug 14, 2018

If x: T | MyClass<T>, then Flow infers from x instanceof MyClass
that x: MyClass<T>. This is unsound: T could be MyClass<U> for
some other U, so that in fact x: MyClass<MyClass<U>>.

Here is an example of how this can go wrong:

// @flow
class Box<T> {
  +field: T;
  constructor(x) {
    this.field = x;
  }
}

function asBox<T>(x: T | Box<T>): Box<T> {
  if (x instanceof Box) {
    // Here, `x` is refined to be `Box<T>`. This is unsound: `T` could
    // well be `Box<U>` for some other `U`. Example below.
    return x;
  } else {
    // whatever
    return new Box(x);
  }
}

const stringStringBox: Box<Box<string>> = asBox(new Box("wat"));  // unsound!
stringStringBox.field.field.substr(0);  // runtime error
@wchargin
Copy link
Contributor Author

@wchargin wchargin commented Aug 14, 2018

Needless to say, this yields coerce:

// @flow
function coerce<T, U>(t: T): U {
  class B<X> {
    x: X;
    constructor(x) {
      this.x = x;
    }
  }
  function b<X>(x: X | B<X>): B<X> {
    if (x instanceof B) return x;
    else throw new Error("Unreachable.");
  }
  const bb: B<B<{t: T}>> = b(new B({t}));
  if (!(bb.x instanceof B)) {
    return bb.x.t;
  }
  throw new Error("Unreachable.");
}
const twelve: number = coerce("twelve");

@wchargin wchargin changed the title Inference with instanceof and generic unions is unsound Refinement with instanceof and generic unions is unsound Aug 15, 2018
@jbrown215
Copy link
Contributor

@jbrown215 jbrown215 commented Aug 20, 2018

cc @samwgoldman

@jbrown215 jbrown215 self-assigned this Aug 20, 2018
@jbrown215
Copy link
Contributor

@jbrown215 jbrown215 commented Aug 20, 2018

After speaking to @samwgoldman , we think it's possible that we are refining x to Box<any>. I'll take a look at this!

Thank you for reporting, @wchargin!

@samwgoldman
Copy link
Member

@samwgoldman samwgoldman commented Aug 20, 2018

Yeah, see

| PredicateT ((RightP (InstanceofTest, _) | NotP (RightP (InstanceofTest, _))), _) ->

Worth experimenting with using instantiate_poly instead, which creates an implicit type argument (i.e., some fresh tvar instead of any). But I haven't thought about this very hard and I wonder about perf.

@wchargin
Copy link
Contributor Author

@wchargin wchargin commented Aug 21, 2018

Worth experimenting with using instantiate_poly instead, which creates an implicit type argument (i.e., some fresh tvar instead of any).

Makes sense; this sounds like the right semantics.

It would be nice for string | Box<T> to be refined to Box<T> instead of Box<?> under an instanceof check; it’s not clear to me whether instantiate_poly would admit that. But if it’s a tradeoff between that incompleteness and this unsoundness, I’d happily take the incompleteness.

@wchargin wchargin added bug Typing: soundness and removed bug Typing: soundness labels Feb 6, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Typing: soundness
Projects
None yet
Development

No branches or pull requests

3 participants