-
Notifications
You must be signed in to change notification settings - Fork 3k
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
classname<T> and private covariant type permits type violation #7216
Comments
Hey @dlreeves this is interesting -- can you make sure Andrew Kennedy sees this, seems like this sort of thing he's been looking into recently. And maybe also tell him to sign up for GitHub and put the email address he commits with on the account and use our internal tool to add him as a contributor to HHVM so I can cc him properly :-P |
I've stared at this a bit longer, and I think allowing From my understanding, usually the combined fact of the constructor only being executed exactly once at instantiation, and the calling scope knowing exactly what class is being instantiated, implies that the constructor can't modify type. |
Another good catch! Even without classname, there is a problem with the semantics of "private" (meaning accessible from the code of the class but at any instantiation). Consider the code below, which breaks the type system. (Scala has a notion of "object-private" to avoid the typehole. See http://www.scala-lang.org/files/archive/spec/2.11/04-basic-declarations-and-definitions.html#variance-annotations. I'm considering doing something similar in Hack.) <?hh // strict
class Box<+T> {
// OK, we've got a private field whose type involves the covariant T
public function __construct(private T $elem) {
}
// As usual, a (safe) getter method
public function get(): T { return $this->elem; }
// Private gives us access to arbitrary instances of Box, even in static
// methods. Note the use of covariant subtyping to put a string in
// a Box<mixed>
public static function updateAsString(Box<mixed> $x, string $s) : void {
$x->elem = $s;
}
// We can now use this method to overwrite an integer with a string
// but return it as an integer
public static function morphIntToString(int $i) : int {
$x = new Box($i);
Box::updateAsString($x, 'hey you turned me into a string');
return $x->get();
}
// Actually do it
public static function useBox(): void {
$i = Box::morphIntToString(23);
echo('this should be an integer: ' . $i);
}
} |
The issue with object-private above is orthogonal. Your analysis is good. One small note: B itself does not have to be covariant. It's enough to have a constraint on the parameter and covariance in the superclass. What if |
I think what you suggest with
Still, in |
HHVM Version
Standalone code, or other way to reproduce the problem
Define a class covariant on one of its type parameters and assert the constructor is consistent:
Then extend that class and constrain that same type parameter to a derived type, and act on that type in some methods:
Cast the
classname
of the extended class to theclassname
of the base class parameterized with a supertype of the extended class's constraint. Invoke a method that acts on the type parameter in the extended class.This is where B needs to be covariant on
T
: the typechecker doesn't allow this cast if it isn't.Expected result
Some sort of variance violation? I don't know which is the more illegal step: allowing private properties of a covariant type, or the classname cast.
Actual result
No errors!
from the typechecker, butfrom executing
C::foo()
.The text was updated successfully, but these errors were encountered: