[ Typechecker ] Returning an unbound generic `T` does not create Hack errors, allowing for unsound use of `T`
- Dominant language
- C++
- Stars
- 18.7k
- Forks
- 3.1k
- Avg merge
- 1h 47m
- Merged PRs (30d)
- 2
Description
**Describe the bug**
**Standalone code, or other way to reproduce the problem**
```HACK
// Simplified case
final class Wrapper {
private function __construct(private T $value) {}
// $x is of type `this = Wrapper`, but to the caller,
// `T` has not been constrained in any meaningful way.
public static function getWrapperOfNull(): Wrapper {
$x = new static(null);
return $x;
}
public function get(): T {
return $this->value;
}
}
function takes_string(string $_): void {}
<<__EntryPoint>>
function main(): void {
// $wrapper1 has a hover type of Wrapper
$wrapper1 = Wrapper::getWrapperOfNull();
takes_string($wrapper1->get());
// $wrapper2 has a hover type of Wrapper<{suggest:nothing}>
$wrapper2 = Wrapper::getWrapperOfNull();
$wrapper2->get()->notEnoughTypeInformationForT();
}
```
Steps to reproduce the behavior:
1. Invoke hh_client, observe an error for `->notEnoughTypeInformationForT()`, but not for `takes_string($wrapper1->get())`.
2. Running this file results in a TypeError, expected `string` got `null` on line `takes_string($wrapper1->get())`.
**Expected behavior**
Hack should tell you that `Wrapper::getWrapperOfNull()` has a unknown type `T` that can not be inferred. This would ideally be done at the declaration site, as the problem lies there. That declaration does not make sense.
**Actual behavior**
```
Typing[4297] Was expecting an object but type is unknown [1]
-> It is unknown because type parameter T of Wrapper could not be determined. Please add explicit type parameters to the invocation of Wrapper [2]
-> via this generic T [3]
-> via this generic T [4]
wrapper.hack:26:5
5 | // $x is of type `this = Wrapper`, but to the caller,
6 | // `T` has not been contrained in any meaningful way.
[3] 7 | public static function getWrapperOfNull(): Wrapper {
8 | $x = new static(null);
9 | return $x;
10 | }
11 |
[4] 12 | public function get(): T {
13 | return $this->value;
14 | }
:
23 | takes_string($wrapper1->get());
24 | // $wrapper2 has a hover type of Wrapper<{suggest:nothing}>
[2] 25 | $wrapper2 = Wrapper::getWrapperOfNull();
[1] 26 | $wrapper2->get()->notEnoughTypeInformationForT();
27 | }
1 error found.
```
**Environment**
- Operating system
> Ubuntu 18.04
- Installation method
> hhvm/hhvm
- HHVM Version
```
HipHop VM 4.137.0 (rel) (non-lowptr)
Compiler: 1637699775_613884736
Repo schema: d02629a79e77aac6dfcb864bc85461ba514c6b50
hackc-261ee251a4f119d21a986be5fb0c88d2e404b515-4.137.0
```
**Additional context**
I find it interesting that Hack knows that `T` is unbound, as `T` is not really `nothing` when you call a method on it (which would be fine with a _real_ `nothing`), but it seems to be fine when passing it along, since `nothing` "extends" `string`.
Contributor guide
Research direction
Start with the standalone Hack reproducer in the issue and run hh_client to compare the two calls involving Wrapper. Trace the typechecker's handling of the unbound generic at getWrapperOfNull(); done means Hack reports the unconstrained T at the declaration or otherwise rejects the unsound use, while preserving the existing diagnostic for the second call.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100