facebook / facebook/hhvm

[ TypeChecker ] Shapes::at() does not generate an error when a field is known to not exist if the given key is a class constant

Open
#8,869 0 comments 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**
Indexing into a shape using `Shapes::at($shape, SomeClass::SOME_CONSTANT)` silently returns `nothing` instead of emitting a typechecker error.

**Standalone code, or other way to reproduce the problem**
```HACK
final class MyClass {
const string A = 'A';
const string B = 'B';
}

function shape_with_class_const_fields(): shape(MyClass::A => string) {
return shape(MyClass::A => 'A');
}

function shape_with_plain_string_fields(): shape('A' => string) {
return shape('A' => 'A');
}

function indexing_into_shape(): void {
$_strange_since_myclass_b_is_known_to_not_exist = Shapes::at(shape_with_class_const_fields(), MyClass::B);
$_even_stranger_since_myclass_is_not_related_to_this_shape = Shapes::at(shape_with_plain_string_fields(), MyClass::B);

// Typing[4251] You are calling Shapes::at() on a field known to not exist Hack(4251)
// file.hack(6, 43): The field A is not defined in this shape
$_expected_this_shape_has_no_plain_string_keys = Shapes::at(shape_with_class_const_fields(), 'A');
// Typing[4251] You are calling Shapes::at() on a field known to not exist Hack(4251)
// file.hack(5, 44): The field B is not defined in this shape
$_expected_this_shape_has_string_a_but_not_b = Shapes::at(shape_with_plain_string_fields(), 'B');
}
```
Steps to reproduce the behavior:
1. hh_client

**Expected behavior**

All these cases should result in an error.

**Actual behavior**

Indexing with a class constant is not a typechecker error. At runtime this throws.

Copy-paste output, or add a screenshot to illustrate what actually happens. Copy-pasted text output (e.g. from `hhvm` or `hh_client`) is preferred to screenshots.

```
Typing[4251] You are calling Shapes::at() on a field known to not exist [1]
-> The field A is not defined in this shape [2]

file.hack:20:54
4 | }
5 |
[2] 6 | function shape_with_class_const_fields(): shape(MyClass::A => string) {
7 | return shape(MyClass::A => 'A');
8 | }
:
18 | // Typing[4251] You are calling Shapes::at() on a field known to not exist Hack(4251)
19 | // file.hack(6, 43): The field A is not defined in this shape
[1] 20 | $_expected_this_shape_has_no_plain_string_keys = Shapes::at(shape_with_class_const_fields(), 'A');
21 | // Typing[4251] You are calling Shapes::at() on a field known to not exist Hack(4251)
22 | // file.hack(5, 44): The field B is not defined in this shape

Typing[4251] You are calling Shapes::at() on a field known to not exist [1]
-> The field B is not defined in this shape [2]

file.hack:23:52
8 | }
9 |
[2] 10 | function shape_with_plain_string_fields(): shape('A' => string) {
11 | return shape('A' => 'A');
12 | }
:
21 | // Typing[4251] You are calling Shapes::at() on a field known to not exist Hack(4251)
22 | // file.hack(5, 44): The field B is not defined in this shape
[1] 23 | $_expected_this_shape_has_string_a_but_not_b = Shapes::at(shape_with_plain_string_fields(), 'B');
24 | }

2 errors found.
```

**Environment**
- Operating system
> 'Ubuntu 18.04'
- Installation method
> 'hhvm/hhvm on dockerhub'
- HHVM Version
```
HipHop VM 4.121.0 (rel) (non-lowptr)
Compiler: 1627924236_151424549
Repo schema: 10a34637ad661d98ba3344717656fcc76209c2f8
hackc-d5e69b1378e833da7a831b4ced2e3f28824d6fad-4.121.0
```

**Additional context**
We use class constant shapes if the field name needs to be changeable in the future. Mostly at API boundaries. These kinds of shapes have a lot of optional fields, which invites the use of `Shapes::at()`. This makes not having the typechecker correct us here extra difficult to deal with.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.