facebook / facebook/hhvm

[ Typechecker ] Returning an unbound generic `T` does not create Hack errors, allowing for unsound use of `T`

Open
#8,954 1 comment 0 reactions 0 assignees View on GitHub
hack
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.