Skip to content
This repository was archived by the owner on Sep 1, 2023. It is now read-only.

Commit 9807a0a

Browse files
authored
Feature/support null and nonnull (#49)
* Add failing test for null type * Add NullSpec with tests * Add support for kind OF_NULL to reified generics * No need for prettyType() * Add test for nested nulls * Let's add support for nonnull, while we're at it
1 parent 4a38eaf commit 9807a0a

File tree

7 files changed

+207
-0
lines changed

7 files changed

+207
-0
lines changed

src/TypeSpec.hack

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,14 @@ function mixed(): TypeSpec<mixed> {
129129
return new __Private\MixedSpec();
130130
}
131131

132+
function nonnull(): TypeSpec<nonnull> {
133+
return new __Private\NonNullSpec();
134+
}
135+
136+
function null(): TypeSpec<null> {
137+
return new __Private\NullSpec();
138+
}
139+
132140
function nullable<T>(TypeSpec<T> $inner): TypeSpec<?T> {
133141
return new __Private\NullableSpec($inner);
134142
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright (c) 2016, Fred Emmott
3+
* Copyright (c) 2017-present, Facebook, Inc.
4+
* All rights reserved.
5+
*
6+
* This source code is licensed under the MIT license found in the
7+
* LICENSE file in the root directory of this source tree.
8+
*
9+
*/
10+
11+
namespace Facebook\TypeSpec\__Private;
12+
13+
use type Facebook\TypeAssert\IncorrectTypeException;
14+
use type Facebook\TypeSpec\TypeSpec;
15+
16+
final class NonNullSpec extends TypeSpec<nonnull> {
17+
use NoCoercionSpecTrait<nonnull>;
18+
19+
<<__Override>>
20+
public function assertType(mixed $value): nonnull {
21+
if ($value is null) {
22+
throw IncorrectTypeException::withValue(
23+
$this->getTrace(),
24+
$this->toString(),
25+
$value,
26+
);
27+
}
28+
return $value;
29+
}
30+
31+
<<__Override>>
32+
public function toString(): string {
33+
return 'nonnull';
34+
}
35+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright (c) 2016, Fred Emmott
3+
* Copyright (c) 2017-present, Facebook, Inc.
4+
* All rights reserved.
5+
*
6+
* This source code is licensed under the MIT license found in the
7+
* LICENSE file in the root directory of this source tree.
8+
*
9+
*/
10+
11+
namespace Facebook\TypeSpec\__Private;
12+
13+
use type Facebook\TypeAssert\IncorrectTypeException;
14+
use type Facebook\TypeSpec\TypeSpec;
15+
16+
final class NullSpec extends TypeSpec<null> {
17+
use NoCoercionSpecTrait<null>;
18+
19+
<<__Override>>
20+
public function assertType(mixed $value): null {
21+
if ($value is nonnull) {
22+
throw IncorrectTypeException::withValue(
23+
$this->getTrace(),
24+
$this->toString(),
25+
$value,
26+
);
27+
}
28+
return null;
29+
}
30+
31+
<<__Override>>
32+
public function toString(): string {
33+
return 'null';
34+
}
35+
}

src/TypeSpec/__Private/from_type_structure.hack

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,12 @@ function from_type_structure<T>(TypeStructure<T> $ts): TypeSpec<T> {
214214
$enum = TypeAssert\not_null($ts['classname']);
215215
/* HH_IGNORE_ERROR[4323] */
216216
return new EnumSpec($enum);
217+
case TypeStructureKind::OF_NULL:
218+
/* HH_IGNORE_ERROR[4110] unsafe generics */
219+
return new NullSpec();
220+
case TypeStructureKind::OF_NONNULL:
221+
/* HH_IGNORE_ERROR[4110] unsafe generics */
222+
return new NonNullSpec();
217223
case TypeStructureKind::OF_UNRESOLVED:
218224
throw new UnsupportedTypeException('OF_UNRESOLVED');
219225
default:

tests/NonNullSpecTest.hack

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright (c) 2016, Fred Emmott
3+
* Copyright (c) 2017-present, Facebook, Inc.
4+
* All rights reserved.
5+
*
6+
* This source code is licensed under the MIT license found in the
7+
* LICENSE file in the root directory of this source tree.
8+
*
9+
*/
10+
11+
namespace Facebook\TypeAssert;
12+
13+
use namespace Facebook\TypeSpec;
14+
use type Facebook\TypeSpec\TypeSpec;
15+
16+
final class NonNullSpecTest extends TypeSpecTest<nonnull> {
17+
<<__Override>>
18+
public function getTypeSpec(): TypeSpec<nonnull> {
19+
return TypeSpec\nonnull();
20+
}
21+
22+
<<__Override>>
23+
public function getValidCoercions(): vec<(mixed, nonnull)> {
24+
return vec[
25+
tuple(0, 0),
26+
tuple('', ''),
27+
tuple("\0", "\0"),
28+
tuple(\STDIN, \STDIN),
29+
tuple(false, false),
30+
tuple(vec[], vec[]),
31+
];
32+
}
33+
34+
<<__Override>>
35+
public function getInvalidCoercions(): vec<(mixed)> {
36+
return vec[
37+
tuple(null),
38+
];
39+
}
40+
41+
<<__Override>>
42+
public function getToStringExamples(): vec<(TypeSpec<nonnull>, string)> {
43+
return vec[
44+
tuple(TypeSpec\nonnull(), 'nonnull'),
45+
];
46+
}
47+
}

tests/NullSpecTest.hack

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright (c) 2016, Fred Emmott
3+
* Copyright (c) 2017-present, Facebook, Inc.
4+
* All rights reserved.
5+
*
6+
* This source code is licensed under the MIT license found in the
7+
* LICENSE file in the root directory of this source tree.
8+
*
9+
*/
10+
11+
namespace Facebook\TypeAssert;
12+
13+
use namespace Facebook\TypeSpec;
14+
use type Facebook\TypeSpec\TypeSpec;
15+
16+
final class NullSpecTest extends TypeSpecTest<null> {
17+
<<__Override>>
18+
public function getTypeSpec(): TypeSpec<null> {
19+
return TypeSpec\null();
20+
}
21+
22+
<<__Override>>
23+
public function getValidCoercions(): vec<(mixed, null)> {
24+
return vec[
25+
tuple(null, null),
26+
];
27+
}
28+
29+
<<__Override>>
30+
public function getInvalidCoercions(): vec<(mixed)> {
31+
return vec[
32+
tuple(0),
33+
tuple(''),
34+
tuple("\0"),
35+
tuple(\STDIN),
36+
tuple(false),
37+
tuple(vec[]),
38+
];
39+
}
40+
41+
<<__Override>>
42+
public function getToStringExamples(): vec<(TypeSpec<null>, string)> {
43+
return vec[
44+
tuple(TypeSpec\null(), 'null'),
45+
];
46+
}
47+
}

tests/ReifiedGenericsTest.hack

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,33 @@ final class ReifiedGenericsTest extends \Facebook\HackTest\HackTest {
6767
expect(TypeSpec\of<shape('foo' => vec<string>, ...)>()->toString())
6868
->toEqual("shape(\n 'foo' => HH\\vec<string>,\n ...\n)");
6969
}
70+
71+
public function testNullType(): void {
72+
expect(TypeSpec\of<null>()->toString())->toEqual('null');
73+
expect(
74+
TypeSpec\of<dict<string, ?dict<string, ?dict<string, null>>>>()
75+
->toString(),
76+
)->toEqual('HH\dict<string, ?HH\dict<string, ?HH\dict<string, null>>>');
77+
}
78+
79+
public function testNonNullType(): void {
80+
expect(TypeSpec\of<nonnull>()->toString())->toEqual('nonnull');
81+
expect(
82+
TypeSpec\of<shape(
83+
?'optional_but_never_null' => nonnull,
84+
'required_but_may_be_null' => mixed,
85+
'neither' => nonnull,
86+
?'both' => mixed,
87+
)>()
88+
->toString(),
89+
)->toEqual(<<<'TYPE'
90+
shape(
91+
?'optional_but_never_null' => nonnull,
92+
'required_but_may_be_null' => mixed,
93+
'neither' => nonnull,
94+
?'both' => mixed,
95+
)
96+
TYPE
97+
);
98+
}
7099
}

0 commit comments

Comments
 (0)