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

Instantiation of intersections not checked for abstract restriction #4559

Closed
sophiajt opened this issue Aug 31, 2015 · 9 comments
Closed

Instantiation of intersections not checked for abstract restriction #4559

sophiajt opened this issue Aug 31, 2015 · 9 comments
Labels
Bug A bug in TypeScript Won't Fix The severity and priority of this issue do not warrant the time or complexity needed to fix it

Comments

@sophiajt
Copy link
Contributor

Say I wanted to create two partial classes, merge them together, and instantiate the result:

abstract class Left {
    left() { return this.right(); }
    abstract right(): number; 
}

abstract class Right {
    abstract left() : number;
    rright(){ return this.left(); }     
}

function merge<A, B>(a: A, b: B) : A & B { return undefined; }

var ab = new(merge(Left, Right));

We currently don't catch the error that we're new'ing something that is abstract. Notice the 'rright' in the Right, so that intersection can't really satisfy the abstract requirements from Left.

@sophiajt sophiajt changed the title Abstract instantiation not checked for intersections Instantiation of intersections not checked for abstract restriction Aug 31, 2015
@mhegazy mhegazy added the Bug A bug in TypeScript label Aug 31, 2015
@weswigham
Copy link
Member

Let's say my merge function did make it constructable (say, by returning a constructor function which calls both superclasses) - how would we represent that?

IMO, this makes sense since abstract simply doesn't work with the type arithmetic. Once you start claiming to be merging types, the abstract restriction on the ctor is gone since we don't know the semantics of how the merge has been performed.

@sophiajt
Copy link
Contributor Author

@weswigham - here's the mental model I've been using:

abstract methods on an abstract class are kinda like imports of a module. Once you satisfy the imports, you know you can safely use the module. In the case of abstract classes, once you've implemented the abstract parts, you know it's safe to execute the class.

If we merge to partial classes together that satisfy each other's requirements, it should be possible to create something that can be safely constructed and used.

@DanielRosenwasser
Copy link
Member

IMO, this makes sense since abstract simply doesn't work with the type arithmetic.

I don't see why it couldn't. Here's the idea:

Union types

If given A | B, if either A or B are abstract, then A | B is abstract as well. This means that any value of type A | B is not constructable.

Intersection types

If given A & B, any property one of A or B that is abstract produces an analogous property in A & B unless there exists in a respective property in the other type that is not abstract.

If any one property in A & B is abstract, then A & B itself becomes abstract, and any value of type A & B is not constructable.

@weswigham
Copy link
Member

Yes, we can say that semantically that's how it can work, but think about how actual intersection types get made in JS - it's perfectly reasonable to take in two "abstract" classes and create a concrete one from them. How do I represent that in the typesystem if abstractness starts propagating with the types?

@weswigham
Copy link
Member

Like if I actually implement that merge function up there like so:

function merge<A, B>(a: A, b: B) : A & B {
  let merged = function() {
    a.call(this);
    b.call(this);
  }
  Object.assign(merged.prototype, a.prototype, b.prototype);
  return merged;
}

The result is never going to be "abstract", in the strict sense, and given the implementation, it's perfectly reasonable to expect to be able to construct the result (despite any grievances about missing abstract members). So if I want to say 'Yes, the result is constructable, despite remaining potential abstractness', how do I indicate that in this new scenario?

@DanielRosenwasser
Copy link
Member

function merge<A, B>(a: A, b: B) : A & B {

I think you meant to return a type that produces the intersection when constructed, not the intersection of two constructors.

it's perfectly reasonable to take in two "abstract" classes and create a concrete one from them

Why is that perfectly reasonable? A major utility of abstract methods is to allow other methods in your object to call them assuming that they exist. If a resultant type is missing its abstract methods, then it's likely the user wants to know about that. You shouldn't be able to construct such an object.

So if I want to say 'Yes, the result is constructable, despite remaining potential abstractness', how do I indicate that in this new scenario?

You merge it with an object that has a concrete implementation, or you add a type annotation/assertion for a new type that doesn't have abstract members. You should need an escape hatch to say "yes, I know what I'm doing" here.

@weswigham
Copy link
Member

I think you meant to return a type that produces the union when constructed, not the union of two constructors.

Yeah; I was just copying the declaration line from above, sorry. Rewriting it, I found it impossible to type it correctly without using an any cast, actually:

function merge<A extends new() => void, B extends new() => void>(a: A, b: B) : new() => A & B {
  let merged = function() {
    a.call(this);
    b.call(this);
  }
  Object.assign(merged.prototype, a.prototype, b.prototype);
  return <new() => A & B><any>merged;
}

Which feels awkward.

You merge it with an object that has a concrete implementation, or you add a type annotation/assertion for a new type that doesn't have abstract members. You should need an escape hatch to say "yes, I know what I'm doing" here.

Yeah - what I'm trying to say is that we don't have that escape hatch for types built from intersection types. Unless we're considering casts to any as a valid way of escaping things like that. Which feels awkward.

@jbondc
Copy link
Contributor

jbondc commented Sep 17, 2015

Pretty much comes down to resolving conflicts #4805

@mhegazy mhegazy added the Won't Fix The severity and priority of this issue do not warrant the time or complexity needed to fix it label Oct 9, 2015
@mhegazy
Copy link
Contributor

mhegazy commented Oct 9, 2015

We will need a concrete example for what scenarios this would be blocking, ideally with a real implementation of the mixin function, rather than a hypothetical one.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Bug A bug in TypeScript Won't Fix The severity and priority of this issue do not warrant the time or complexity needed to fix it
Projects
None yet
Development

No branches or pull requests

5 participants