├── .devcontainer └── devcontainer.json ├── .gitattributes ├── .github └── workflows │ └── build-and-test.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── composer.json ├── hh_autoload.json ├── hhast-lint.json └── src ├── IncorrectTypeException.hack ├── TypeAssert.hack ├── TypeCoerce.hack ├── TypeCoercionException.hack ├── TypeSpec.hack ├── TypeSpec ├── Trace.hack └── __Private │ ├── ArrayKeySpec.hack │ ├── BoolSpec.hack │ ├── ClassnameSpec.hack │ ├── DarraySpec.hack │ ├── DictSpec.hack │ ├── EnumSpec.hack │ ├── ExceptionWithSpecTraceTrait.hack │ ├── FloatSpec.hack │ ├── InstanceOfSpec.hack │ ├── IntSpec.hack │ ├── KeyedTraversableSpec.hack │ ├── KeysetSpec.hack │ ├── MapSpec.hack │ ├── MixedSpec.hack │ ├── NoCoercionSpecTrait.hack │ ├── NonNullSpec.hack │ ├── NullSpec.hack │ ├── NullableSpec.hack │ ├── NumSpec.hack │ ├── OptionalSpec.hack │ ├── ResourceSpec.hack │ ├── SetSpec.hack │ ├── ShapeSpec.hack │ ├── StringSpec.hack │ ├── TraversableSpec.hack │ ├── TupleSpec.hack │ ├── UnionSpec.hack │ ├── UnknownFieldsMode.hack │ ├── UntypedArraySpec.hack │ ├── VArrayOrDArraySpec.hack │ ├── VarraySpec.hack │ ├── VecSpec.hack │ ├── VectorSpec.hack │ ├── from_type_structure.hack │ └── stringish_cast.hack └── UnsupportedTypeException.hack /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. 2 | { 3 | "name": "Hack", 4 | "runArgs": [ 5 | "--init" 6 | ], 7 | "image": "hhvm/hhvm:latest", 8 | 9 | // Set *default* container specific settings.json values on container create. 10 | "userEnvProbe": "loginShell", 11 | 12 | // Add the IDs of extensions you want installed when the container is created. 13 | "extensions": [ 14 | "pranayagarwal.vscode-hack" 15 | ], 16 | 17 | // Use 'postCreateCommand' to run commands after the container is created. 18 | "postCreateCommand": "curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/bin --filename=composer && composer install" 19 | 20 | } 21 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | tests/ export-ignore 2 | .hhconfig export-ignore 3 | .hhvmconfig.hdf export-ignore 4 | *.hack linguist-language=Hack 5 | -------------------------------------------------------------------------------- /.github/workflows/build-and-test.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | on: 3 | push: 4 | pull_request: 5 | schedule: 6 | - cron: '0 15 * * *' 7 | jobs: 8 | build: 9 | name: HHVM ${{matrix.hhvm}} - ${{matrix.os}} 10 | strategy: 11 | # Run tests on all OS's and HHVM versions, even if one fails 12 | fail-fast: false 13 | matrix: 14 | os: [ ubuntu ] 15 | hhvm: 16 | - "4.128" 17 | - latest 18 | - nightly 19 | runs-on: ${{matrix.os}}-latest 20 | steps: 21 | - uses: actions/checkout@v2 22 | - uses: hhvm/actions/hack-lint-test@master 23 | with: 24 | hhvm: ${{matrix.hhvm}} 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .var/ 2 | vendor/ 3 | composer.lock 4 | .*.hhast.*cache 5 | *.swp 6 | *.swo 7 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | Facebook has adopted a Code of Conduct that we expect project participants to adhere to. 4 | Please read the [full text](https://code.facebook.com/pages/876921332402685/open-source-code-of-conduct) 5 | so that you can understand what actions will and will not be tolerated. 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Type-Assert 2 | We want to make contributing to this project as easy and transparent as 3 | possible. 4 | 5 | ## Our Development Process 6 | GitHub is the source of truth for code, issues, and pull requests. 7 | 8 | ## Pull Requests 9 | We actively welcome your pull requests. 10 | 11 | 1. Fork the repo and create your branch from `main`. 12 | 2. If you've added code that should be tested, add tests. 13 | 3. If you've changed APIs, update the documentation. 14 | 4. Ensure the test suite passes. 15 | 5. Make sure your code lints. 16 | 6. If you haven't already, complete the Contributor License Agreement ("CLA"). 17 | 18 | ## Contributor License Agreement ("CLA") 19 | In order to accept your pull request, we need you to submit a CLA. You only need 20 | to do this once to work on any of Facebook's open source projects. 21 | 22 | Complete your CLA here: 23 | 24 | ## Issues 25 | We use GitHub issues to track public bugs. Please ensure your description is 26 | clear and has sufficient instructions to be able to reproduce the issue. 27 | 28 | Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe 29 | disclosure of security bugs. In those cases, please go through the process 30 | outlined on that page and do not file a public issue. 31 | 32 | ## Coding Style 33 | 34 | We use `hackfmt` from the latest release of HHVM to format code; this means: 35 | 36 | * 2 spaces for indentation rather than tabs 37 | * 80 character line length 38 | * Be consistent with existing code 39 | 40 | We do not follow the PSR guidelines. 41 | 42 | ## License 43 | By contributing to Type-Assert, you agree that your contributions 44 | will be licensed under the LICENSE file in the root directory of this source 45 | tree. 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | For Type-Assert software 4 | 5 | Copyright (c) 2016, Fred Emmott. All rights reserved. 6 | Copyright (c) 2017-present, Facebook, Inc. All rights reserved. 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | TypeAssert 2 | ========== 3 | 4 | [![Continuous Integration](https://github.com/hhvm/type-assert/actions/workflows/build-and-test.yml/badge.svg)](https://github.com/hhvm/type-assert/actions/workflows/build-and-test.yml) 5 | 6 | Hack library for converting untyped data to typed data. 7 | 8 | Warning for `TypeAssert\matches_type_structure()` 9 | -------------------------------------------------- 10 | 11 | `TypeStructure`, `type_structure()`, and `ReflectionTypeAlias::getTypeStructures()` 12 | are experimental features of HHVM, and not supported by Facebook or the HHVM team. 13 | This means that `matches_type_structure()` may need to be removed in a future release 14 | without warning. 15 | 16 | We strongly recommend moving to `TypeAssert\matches()` and 17 | `TypeCoerce\match()` instead. 18 | 19 | Installation 20 | ------------ 21 | 22 | ``` 23 | composer require hhvm/type-assert 24 | ``` 25 | 26 | Usage 27 | ----- 28 | 29 | TypeAssert provides functions that take a mixed input, and will 30 | either return it unmodified (but with type data) or throw an exception; for example: 31 | 32 | ```Hack 33 | (?T): T` 53 | - `instance_of(classname, mixed): T` 54 | - `classname_of(classname, mixed): classname` 55 | - `matches(mixed): T` 56 | - `matches_type_structure(TypeStructure, mixed): T` 57 | 58 | Coercion 59 | -------- 60 | 61 | TypeAssert also contains the `Facebook\TypeCoerce` namespace, which includes a 62 | similar set of functions: 63 | 64 | - `string(mixed): string` 65 | - `int(mixed): int` 66 | - `float(mixed): float` 67 | - `bool(mixed): bool` 68 | - `resource(mixed): resource` 69 | - `num(mixed): num` 70 | - `arraykey(mixed): arraykey` 71 | - `match(mixed): T` 72 | - `match_type_structure(TypeStructure, mixed): T` 73 | 74 | These will do 'safe' transformations, such as int-ish strings to int, ints to 75 | strings, arrays to vecs, arrays to dicts, and so on. 76 | 77 | TypeSpec 78 | -------- 79 | 80 | You can also assert/coerce complex types (except for shapes and tuples) without 81 | a type_structure: 82 | 83 | ```Hack 84 | assertType(dict['foo' => 123]); // passes: $x is a dict 93 | $x = $spec->assertType(dict['foo' => '123']); // fails 94 | $x = $spec->assertType(dict[123 => 456]); // fails 95 | $x = $spec->assertType(dict[123 => 456]); // fails 96 | 97 | $x = $spec->coerceType(dict[123 => '456']); // passes: $x is dict['123' => 456]; 98 | ``` 99 | 100 | Shapes and tuples are not supported, as they can not be expressed generically. 101 | 102 | `matches_type_structure(TypeStructure, mixed): T` 103 | ----------------------------------------------------- 104 | 105 | Asserts that a variable matches the given type structure; these can be arbitrary 106 | nested shapes. This is particular useful for dealing with JSON responses. 107 | 108 | ```Hack 109 | int, 116 | 'user' => string, 117 | 'data' => shape( 118 | /* ... */ 119 | ), 120 | ); 121 | 122 | public static function getAPIResponse(): self::TAPIResponse { 123 | $json_string = file_get_contents('https://api.example.com'); 124 | $array = json_decode($json_string, /* associative = */ true); 125 | return TypeAssert\matches_type_structure( 126 | type_structure(self::class, 'TAPIResponse'), 127 | $array, 128 | ); 129 | } 130 | } 131 | ``` 132 | 133 | You can use `type_structure()` to get a `TypeStructure` for a type constant, 134 | or `ReflectionTypeAlias::getTypeStructure()` for top-level type aliases. 135 | 136 | `not_null(?T): T` 137 | --------------------- 138 | 139 | Throws if it's null, and refines the type otherwise - for example: 140 | 141 | ```Hack 142 | string 150 | needs_int(TypeAssert\not_null($bar)); // ?int => int 151 | } 152 | ``` 153 | 154 | `is_instance_of(classname, mixed): T` 155 | ----------------------------------------- 156 | 157 | Asserts that the input is an object of the given type; for example: 158 | 159 | ```Hack 160 | (classname, mixed): classname` 175 | ------------------------------------------------------------ 176 | 177 | Asserts that the input is the name of a child of the specified class, or 178 | implements the specified interface. 179 | 180 | ```Hack 181 | > 189 | public static function doStuff(): void { 190 | // specialize here 191 | } 192 | } 193 | 194 | function needs_foo_class(classname $foo): void { 195 | $foo::doStuff(); 196 | } 197 | 198 | function main(mixed $class): void { 199 | needs_foo_class(TypeAssert::is_classname_of(Foo::class, $class)); 200 | } 201 | 202 | main(Bar::class); 203 | ``` 204 | 205 | 206 | Credit 207 | ------ 208 | 209 | This library is a reimplementation of ideas from: 210 | 211 | - @admdikramr 212 | - @ahupp 213 | - @dlreeves 214 | - @periodic1236 215 | - @schrockn 216 | 217 | Security Issues 218 | --------------- 219 | 220 | We use GitHub issues to track public bugs. Please ensure your description is 221 | clear and has sufficient instructions to be able to reproduce the issue. 222 | 223 | Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe 224 | disclosure of security bugs. In those cases, please go through the process 225 | outlined on that page and do not file a public issue. 226 | 227 | License 228 | ------- 229 | 230 | Type-Assert is MIT-licensed. 231 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hhvm/type-assert", 3 | "description": "Convert untyped data to typed data", 4 | "license": "MIT", 5 | "keywords": ["hack", "TypeAssert"], 6 | "require": { 7 | "hhvm": "^4.128" 8 | }, 9 | "extra": { 10 | "branch-alias": { 11 | "dev-master": "4.x-dev", 12 | "dev-main": "4.x-dev" 13 | } 14 | }, 15 | "require-dev": { 16 | "hhvm/hhvm-autoload": "^2.0|^3.0", 17 | "facebook/fbexpect": "^2.0.0", 18 | "hhvm/hacktest": "^2.0", 19 | "hhvm/hhast": "^4.0" 20 | }, 21 | "config": { 22 | "allow-plugins": { 23 | "hhvm/hhvm-autoload": true 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /hh_autoload.json: -------------------------------------------------------------------------------- 1 | { 2 | "roots": [ 3 | "src/" 4 | ], 5 | "devRoots": [ 6 | "tests/" 7 | ], 8 | "devFailureHandler": "Facebook\\AutoloadMap\\HHClientFallbackHandler", 9 | "useFactsIfAvailable": true 10 | } 11 | -------------------------------------------------------------------------------- /hhast-lint.json: -------------------------------------------------------------------------------- 1 | { 2 | "roots": [ "src/", "tests/" ], 3 | "builtinLinters": "all" 4 | } 5 | -------------------------------------------------------------------------------- /src/IncorrectTypeException.hack: -------------------------------------------------------------------------------- 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 type Facebook\TypeSpec\Trace; 14 | use type Facebook\TypeSpec\__Private\ExceptionWithSpecTraceTrait; 15 | 16 | final class IncorrectTypeException extends \Exception { 17 | use ExceptionWithSpecTraceTrait; 18 | 19 | public function __construct( 20 | private Trace $specTrace, 21 | private string $expected, 22 | private string $actual, 23 | ) { 24 | $message = \sprintf('Expected %s, got %s', $expected, $actual); 25 | parent::__construct($message); 26 | } 27 | 28 | public function getSpecTrace(): Trace { 29 | return $this->specTrace; 30 | } 31 | 32 | public static function withType( 33 | Trace $trace, 34 | string $expected_type, 35 | string $actual_type, 36 | ): IncorrectTypeException { 37 | return new self( 38 | $trace, 39 | \sprintf("type '%s'", $expected_type), 40 | \sprintf("type '%s'", $actual_type), 41 | ); 42 | } 43 | 44 | public static function withValue( 45 | Trace $trace, 46 | string $expected_type, 47 | mixed $value, 48 | ): IncorrectTypeException { 49 | $actual_type = \gettype($value); 50 | if ($actual_type === 'object') { 51 | $actual_type = \get_class($value); 52 | } 53 | return self::withType($trace, $expected_type, $actual_type); 54 | } 55 | 56 | public function getExpectedType(): string { 57 | return $this->expected; 58 | } 59 | 60 | public function getActualType(): string { 61 | return $this->actual; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/TypeAssert.hack: -------------------------------------------------------------------------------- 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 | 15 | function string(mixed $x): string { 16 | return TypeSpec\string()->assertType($x); 17 | } 18 | 19 | function int(mixed $x): int { 20 | return TypeSpec\int()->assertType($x); 21 | } 22 | 23 | function float(mixed $x): float { 24 | return TypeSpec\float()->assertType($x); 25 | } 26 | 27 | function bool(mixed $x): bool { 28 | return TypeSpec\bool()->assertType($x); 29 | } 30 | 31 | function resource(mixed $x): resource { 32 | return TypeSpec\resource()->assertType($x); 33 | } 34 | 35 | function num(mixed $x): num { 36 | return TypeSpec\num()->assertType($x); 37 | } 38 | 39 | function arraykey(mixed $x): arraykey { 40 | return TypeSpec\arraykey()->assertType($x); 41 | } 42 | 43 | function not_null(?T $x): T { 44 | if ($x === null) { 45 | throw new IncorrectTypeException(new TypeSpec\Trace(), 'not-null', 'null'); 46 | } 47 | return $x; 48 | } 49 | 50 | function instance_of(classname $type, mixed $what): T { 51 | return TypeSpec\instance_of($type)->assertType($what); 52 | } 53 | 54 | function classname_of(classname $expected, string $what): classname { 55 | return TypeSpec\classname($expected)->assertType($what); 56 | } 57 | 58 | function matches_type_structure(TypeStructure $ts, mixed $value): T { 59 | return TypeSpec\__Private\from_type_structure($ts)->assertType($value); 60 | } 61 | 62 | function matches(mixed $value): T { 63 | return TypeSpec\of()->assertType($value); 64 | } 65 | -------------------------------------------------------------------------------- /src/TypeCoerce.hack: -------------------------------------------------------------------------------- 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\TypeCoerce; 12 | 13 | use namespace Facebook\TypeSpec; 14 | 15 | function string(mixed $x): string { 16 | return TypeSpec\string()->coerceType($x); 17 | } 18 | 19 | function int(mixed $x): int { 20 | return TypeSpec\int()->coerceType($x); 21 | } 22 | 23 | function float(mixed $x): float { 24 | return TypeSpec\float()->coerceType($x); 25 | } 26 | 27 | function bool(mixed $x): bool { 28 | return TypeSpec\bool()->coerceType($x); 29 | } 30 | 31 | function resource(mixed $x): resource { 32 | return TypeSpec\resource()->coerceType($x); 33 | } 34 | 35 | function num(mixed $x): num { 36 | return TypeSpec\num()->coerceType($x); 37 | } 38 | 39 | function arraykey(mixed $x): arraykey { 40 | return TypeSpec\arraykey()->coerceType($x); 41 | } 42 | 43 | function match_type_structure(TypeStructure $ts, mixed $value): T { 44 | return TypeSpec\__Private\from_type_structure($ts)->coerceType($value); 45 | } 46 | 47 | function match(mixed $value): T { 48 | return TypeSpec\of()->coerceType($value); 49 | } 50 | -------------------------------------------------------------------------------- /src/TypeCoercionException.hack: -------------------------------------------------------------------------------- 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 type Facebook\TypeSpec\Trace; 14 | use type Facebook\TypeSpec\__Private\ExceptionWithSpecTraceTrait; 15 | 16 | final class TypeCoercionException extends \Exception { 17 | use ExceptionWithSpecTraceTrait; 18 | 19 | public function __construct( 20 | private Trace $specTrace, 21 | private string $target, 22 | private string $actual, 23 | ) { 24 | $message = \sprintf('Could not coerce %s to type %s', $actual, $target); 25 | parent::__construct($message); 26 | } 27 | 28 | public function getSpecTrace(): Trace { 29 | return $this->specTrace; 30 | } 31 | 32 | public function getTargetType(): string { 33 | return $this->target; 34 | } 35 | 36 | public function getActualType(): string { 37 | return $this->actual; 38 | } 39 | 40 | public static function withValue( 41 | Trace $trace, 42 | string $expected, 43 | mixed $value, 44 | ): this { 45 | return new self( 46 | $trace, 47 | $expected, 48 | \is_object($value) ? \get_class($value) : \gettype($value), 49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/TypeSpec.hack: -------------------------------------------------------------------------------- 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; 12 | 13 | abstract class TypeSpec<+T> { 14 | private ?Trace $trace = null; 15 | 16 | abstract public function coerceType(mixed $value): T; 17 | abstract public function assertType(mixed $value): T; 18 | abstract public function toString(): string; 19 | 20 | public function isOptional(): bool { 21 | return false; 22 | } 23 | 24 | final protected function getTrace(): Trace { 25 | return $this->trace ?? new Trace(); 26 | } 27 | 28 | final protected function withTrace(Trace $trace): TypeSpec { 29 | $new = clone $this; 30 | $new->trace = $trace; 31 | return $new; 32 | } 33 | } 34 | 35 | function arraykey(): TypeSpec { 36 | return new __Private\ArrayKeySpec(); 37 | } 38 | 39 | function bool(): TypeSpec { 40 | return new __Private\BoolSpec(); 41 | } 42 | 43 | function classname(classname $what): TypeSpec> { 44 | return new __Private\ClassnameSpec($what); 45 | } 46 | 47 | function constmap( 48 | TypeSpec $tsk, 49 | TypeSpec $tsv, 50 | ): TypeSpec<\ConstMap> { 51 | return new __Private\MapSpec(\ConstMap::class, $tsk, $tsv); 52 | } 53 | 54 | function constset(TypeSpec $tsv): TypeSpec<\ConstSet> { 55 | return new __Private\SetSpec(\ConstSet::class, $tsv); 56 | } 57 | 58 | function constvector(TypeSpec $inner): TypeSpec<\ConstVector> { 59 | return new __Private\VectorSpec(\ConstVector::class, $inner); 60 | } 61 | 62 | function darray( 63 | TypeSpec $tsk, 64 | TypeSpec $tsv, 65 | ): TypeSpec> { 66 | return new __Private\DarraySpec($tsk, $tsv); 67 | } 68 | 69 | function dict( 70 | TypeSpec $tsk, 71 | TypeSpec $tsv, 72 | ): TypeSpec> { 73 | return new __Private\DictSpec($tsk, $tsv); 74 | } 75 | 76 | function enum(\HH\enumname $what): TypeSpec { 77 | return new __Private\EnumSpec($what); 78 | } 79 | 80 | function float(): TypeSpec { 81 | return new __Private\FloatSpec(); 82 | } 83 | 84 | function instance_of(classname $what): TypeSpec { 85 | return new __Private\InstanceOfSpec($what); 86 | } 87 | 88 | function int(): TypeSpec { 89 | return new __Private\IntSpec(); 90 | } 91 | 92 | function immmap( 93 | TypeSpec $tsk, 94 | TypeSpec $tsv, 95 | ): TypeSpec> { 96 | return new __Private\MapSpec(ImmMap::class, $tsk, $tsv); 97 | } 98 | 99 | function immset(TypeSpec $tsv): TypeSpec> { 100 | return new __Private\SetSpec(ImmSet::class, $tsv); 101 | } 102 | 103 | function immvector(TypeSpec $inner): TypeSpec> { 104 | return new __Private\VectorSpec(ImmVector::class, $inner); 105 | } 106 | 107 | function keyset(TypeSpec $inner): TypeSpec> { 108 | return new __Private\KeysetSpec($inner); 109 | } 110 | 111 | function map( 112 | TypeSpec $tsk, 113 | TypeSpec $tsv, 114 | ): TypeSpec> { 115 | return new __Private\MapSpec(Map::class, $tsk, $tsv); 116 | } 117 | 118 | function mixed(): TypeSpec { 119 | return new __Private\MixedSpec(); 120 | } 121 | 122 | function nonnull(): TypeSpec { 123 | return new __Private\NonNullSpec(); 124 | } 125 | 126 | function null(): TypeSpec { 127 | return new __Private\NullSpec(); 128 | } 129 | 130 | function nullable(TypeSpec $inner): TypeSpec { 131 | return new __Private\NullableSpec($inner); 132 | } 133 | 134 | function num(): TypeSpec { 135 | return new __Private\NumSpec(); 136 | } 137 | 138 | function resource(?string $kind = null): TypeSpec { 139 | return new __Private\ResourceSpec($kind); 140 | } 141 | 142 | function set(TypeSpec $tsv): TypeSpec> { 143 | return new __Private\SetSpec(Set::class, $tsv); 144 | } 145 | 146 | function string(): TypeSpec { 147 | return new __Private\StringSpec(); 148 | } 149 | 150 | function varray(TypeSpec $tsv): TypeSpec> { 151 | return new __Private\VarraySpec($tsv); 152 | } 153 | 154 | function vec(TypeSpec $inner): TypeSpec> { 155 | return new __Private\VecSpec($inner); 156 | } 157 | 158 | function vector(TypeSpec $inner): TypeSpec> { 159 | return new __Private\VectorSpec(Vector::class, $inner); 160 | } 161 | 162 | function varray_or_darray( 163 | TypeSpec $inner, 164 | ): TypeSpec> { 165 | return new __Private\VArrayOrDArraySpec($inner); 166 | } 167 | 168 | function of(): TypeSpec { 169 | return __Private\from_type_structure( 170 | \HH\ReifiedGenerics\get_type_structure(), 171 | ); 172 | } 173 | -------------------------------------------------------------------------------- /src/TypeSpec/Trace.hack: -------------------------------------------------------------------------------- 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; 12 | 13 | final class Trace { 14 | const type TFrame = string; 15 | private vec $frames = vec[]; 16 | 17 | public function withFrame(self::TFrame $frame): this { 18 | $new = clone $this; 19 | $new->frames[] = $frame; 20 | return $new; 21 | } 22 | 23 | public function getFrames(): vec { 24 | return $this->frames; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/TypeSpec/__Private/ArrayKeySpec.hack: -------------------------------------------------------------------------------- 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 | final class ArrayKeySpec extends UnionSpec { 14 | public function __construct() { 15 | parent::__construct('arraykey', new StringSpec(), new IntSpec()); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/TypeSpec/__Private/BoolSpec.hack: -------------------------------------------------------------------------------- 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, TypeCoercionException}; 14 | use type Facebook\TypeSpec\TypeSpec; 15 | 16 | final class BoolSpec extends TypeSpec { 17 | <<__Override>> 18 | public function coerceType(mixed $value): bool { 19 | if ($value is bool) { 20 | return $value; 21 | } 22 | if ($value === 0) { 23 | return false; 24 | } 25 | if ($value === 1) { 26 | return true; 27 | } 28 | throw TypeCoercionException::withValue($this->getTrace(), 'bool', $value); 29 | } 30 | 31 | <<__Override>> 32 | public function assertType(mixed $value): bool { 33 | if ($value is bool) { 34 | return $value; 35 | } 36 | throw IncorrectTypeException::withValue($this->getTrace(), 'bool', $value); 37 | } 38 | 39 | <<__Override>> 40 | public function toString(): string { 41 | return 'bool'; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/TypeSpec/__Private/ClassnameSpec.hack: -------------------------------------------------------------------------------- 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 ClassnameSpec> extends TypeSpec { 17 | use NoCoercionSpecTrait; 18 | 19 | public function __construct(private T $what) { 20 | } 21 | 22 | <<__Override>> 23 | public function assertType(mixed $value): T { 24 | if (($value is string) && \is_a($value, $this->what, /* strings = */ true)) { 25 | /* HH_IGNORE_ERROR[4110] is_a is not understood by Hack */ 26 | return $value; 27 | } 28 | throw 29 | IncorrectTypeException::withValue($this->getTrace(), $this->what, $value); 30 | } 31 | 32 | <<__Override>> 33 | public function toString(): string { 34 | return 'classname<\\'.$this->what.'>'; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/TypeSpec/__Private/DarraySpec.hack: -------------------------------------------------------------------------------- 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, TypeCoercionException}; 14 | use type Facebook\TypeSpec\TypeSpec; 15 | 16 | use namespace HH\Lib\{Dict, Str}; 17 | 18 | final class DarraySpec 19 | extends TypeSpec> { 20 | 21 | public function __construct( 22 | private TypeSpec $tsk, 23 | private TypeSpec $tsv, 24 | ) { 25 | } 26 | 27 | <<__Override>> 28 | public function coerceType(mixed $value): darray { 29 | if (!$value is KeyedTraversable<_, _>) { 30 | throw TypeCoercionException::withValue( 31 | $this->getTrace(), 32 | 'darray', 33 | $value, 34 | ); 35 | } 36 | 37 | $kt = $this->getTrace()->withFrame('darray'); 38 | $vt = $this->getTrace()->withFrame('darray<_, Tv>'); 39 | 40 | return Dict\pull_with_key( 41 | $value, 42 | ($_k, $v) ==> $this->tsv->withTrace($vt)->coerceType($v), 43 | ($k, $_v) ==> $this->tsk->withTrace($kt)->coerceType($k), 44 | ) |> darray($$); 45 | } 46 | 47 | <<__Override>> 48 | public function assertType(mixed $value): darray { 49 | if (/* HH_FIXME[2049] */ /* HH_FIXME[4107] */ !is_darray($value)) { 50 | throw IncorrectTypeException::withValue( 51 | $this->getTrace(), 52 | $this->toString(), 53 | $value, 54 | ); 55 | } 56 | 57 | $kt = $this->getTrace()->withFrame('darray'); 58 | $vt = $this->getTrace()->withFrame('darray<_, Tv>'); 59 | 60 | return Dict\pull_with_key( 61 | $value as KeyedTraversable<_, _>, 62 | ($_k, $v) ==> $this->tsv->withTrace($vt)->assertType($v), 63 | ($k, $_v) ==> $this->tsk->withTrace($kt)->assertType($k), 64 | ) 65 | |> darray($$); 66 | } 67 | 68 | <<__Override>> 69 | public function toString(): string { 70 | return Str\format( 71 | 'darray<%s, %s>', 72 | $this->tsk->toString(), 73 | $this->tsv->toString(), 74 | ); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/TypeSpec/__Private/DictSpec.hack: -------------------------------------------------------------------------------- 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, TypeCoercionException}; 14 | use type Facebook\TypeSpec\TypeSpec; 15 | use namespace HH\Lib\{Dict, Str}; 16 | 17 | final class DictSpec extends TypeSpec> { 18 | 19 | public function __construct( 20 | private TypeSpec $tsk, 21 | private TypeSpec $tsv, 22 | ) { 23 | } 24 | 25 | <<__Override>> 26 | public function coerceType(mixed $value): dict { 27 | if (!$value is KeyedTraversable<_, _>) { 28 | throw TypeCoercionException::withValue( 29 | $this->getTrace(), 30 | 'dict', 31 | $value, 32 | ); 33 | } 34 | 35 | $kt = $this->getTrace()->withFrame('dict'); 36 | $vt = $this->getTrace()->withFrame('dict<_, Tv>'); 37 | 38 | return Dict\pull_with_key( 39 | $value, 40 | ($_k, $v) ==> $this->tsv->withTrace($vt)->coerceType($v), 41 | ($k, $_v) ==> $this->tsk->withTrace($kt)->coerceType($k), 42 | ); 43 | } 44 | 45 | <<__Override>> 46 | public function assertType(mixed $value): dict { 47 | if (!($value is dict<_, _>)) { 48 | throw IncorrectTypeException::withValue( 49 | $this->getTrace(), 50 | 'dict', 51 | $value, 52 | ); 53 | } 54 | 55 | $kt = $this->getTrace()->withFrame('dict'); 56 | $vt = $this->getTrace()->withFrame('dict<_, Tv>'); 57 | 58 | return Dict\pull_with_key( 59 | $value, 60 | ($_k, $v) ==> $this->tsv->withTrace($vt)->assertType($v), 61 | ($k, $_v) ==> $this->tsk->withTrace($kt)->assertType($k), 62 | ); 63 | } 64 | 65 | <<__Override>> 66 | public function toString(): string { 67 | return Str\format( 68 | '%s<%s, %s>', 69 | dict::class, 70 | $this->tsk->toString(), 71 | $this->tsv->toString(), 72 | ); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/TypeSpec/__Private/EnumSpec.hack: -------------------------------------------------------------------------------- 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, TypeCoercionException}; 14 | use type Facebook\TypeSpec\TypeSpec; 15 | 16 | final class EnumSpec extends TypeSpec { 17 | 18 | public function __construct(private \HH\enumname $what) { 19 | } 20 | 21 | <<__Override>> 22 | public function coerceType(mixed $value): T { 23 | $what = $this->what; 24 | try { 25 | return $what::assert($value); 26 | } catch (\UnexpectedValueException $_e) { 27 | throw TypeCoercionException::withValue($this->getTrace(), $what, $value); 28 | } 29 | } 30 | 31 | <<__Override>> 32 | public function assertType(mixed $value): T { 33 | $what = $this->what; 34 | try { 35 | return $what::assert($value); 36 | } catch (\UnexpectedValueException $_e) { 37 | throw IncorrectTypeException::withValue($this->getTrace(), $what, $value); 38 | } 39 | } 40 | 41 | <<__Override>> 42 | public function toString(): string { 43 | return $this->what; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/TypeSpec/__Private/ExceptionWithSpecTraceTrait.hack: -------------------------------------------------------------------------------- 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\TypeSpec\Trace; 14 | use namespace HH\Lib\{C, Vec}; 15 | 16 | trait ExceptionWithSpecTraceTrait { 17 | require extends \Exception; 18 | 19 | abstract public function getSpecTrace(): Trace; 20 | 21 | final public function getMessage(): string { 22 | $message = parent::getMessage(); 23 | $frames = $this->getSpecTrace()->getFrames(); 24 | if (C\is_empty($frames)) { 25 | return $message; 26 | } 27 | return \sprintf( 28 | "%s\nType trace:\n%s\n", 29 | $message, 30 | $frames 31 | |> Vec\reverse($$) 32 | |> Vec\map_with_key($$, ($depth, $frame) ==> '#'.$depth.' '.$frame) 33 | |> \implode("\n", $$), 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/TypeSpec/__Private/FloatSpec.hack: -------------------------------------------------------------------------------- 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, TypeCoercionException}; 14 | use type Facebook\TypeSpec\TypeSpec; 15 | use namespace HH\Lib\Regex; 16 | 17 | final class FloatSpec extends TypeSpec { 18 | 19 | <<__Override>> 20 | public function coerceType(mixed $value): float { 21 | if ($value is float) { 22 | return $value; 23 | } 24 | 25 | if ($value is int) { 26 | return (float)$value; 27 | } 28 | 29 | if ($value is \Stringish) { 30 | $str = stringish_cast($value, __CLASS__.'::'.__METHOD__); 31 | if ($str === '') { 32 | throw TypeCoercionException::withValue( 33 | $this->getTrace(), 34 | 'float', 35 | $value, 36 | ); 37 | } 38 | 39 | //I presume this is here because it is cheaper than the regex. 40 | //Removing this call does not affect the output of tests. 41 | if (\ctype_digit($value)) { 42 | return (float)$str; 43 | } 44 | /*REGEX 45 | At the beginning of a string, find an optional minus. ^-? 46 | Find at least one digit 47 | with an optional period between them or preceeding them. (?:\d*\.)?\d+ 48 | Optionally: Find and e or E followed by an optional minus and at least one digit. (?:[eE]-?\d+)? 49 | The end of the string. $ 50 | */ 51 | if (Regex\matches($str, re"/^-?(?:\\d*\\.)?\\d+(?:[eE]-?\\d+)?$/")) { 52 | return (float)$str; 53 | } 54 | } 55 | throw TypeCoercionException::withValue($this->getTrace(), 'float', $value); 56 | } 57 | 58 | <<__Override>> 59 | public function assertType(mixed $value): float { 60 | if ($value is float) { 61 | return $value; 62 | } 63 | throw IncorrectTypeException::withValue($this->getTrace(), 'float', $value); 64 | } 65 | 66 | <<__Override>> 67 | public function toString(): string { 68 | return 'float'; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/TypeSpec/__Private/InstanceOfSpec.hack: -------------------------------------------------------------------------------- 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 InstanceOfSpec extends TypeSpec { 17 | use NoCoercionSpecTrait; 18 | 19 | public function __construct(private classname $what) { 20 | } 21 | 22 | <<__Override>> 23 | public function assertType(mixed $value): T { 24 | if (\is_a($value, $this->what)) { 25 | /* HH_IGNORE_ERROR[4110] unsafe for generics */ 26 | return $value; 27 | } 28 | throw 29 | IncorrectTypeException::withValue($this->getTrace(), $this->what, $value); 30 | } 31 | 32 | <<__Override>> 33 | public function toString(): string { 34 | return $this->what; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/TypeSpec/__Private/IntSpec.hack: -------------------------------------------------------------------------------- 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, TypeCoercionException}; 14 | use type Facebook\TypeSpec\TypeSpec; 15 | use namespace HH\Lib\Str; 16 | 17 | final class IntSpec extends TypeSpec { 18 | <<__Override>> 19 | public function coerceType(mixed $value): int { 20 | if ($value is int) { 21 | return $value; 22 | } 23 | if ($value is \Stringish) { 24 | $str = stringish_cast($value, __CLASS__.'::'.__METHOD__); 25 | $int = (int)$str; 26 | 27 | // "1234" -(int)-> 1234 -(string)-> "1234" 28 | // "-1234" -(int)-> -1234 -(string)-> "-1234" 29 | // ^^ are the same ^^ 30 | if ($str === (string)$int) { 31 | return $int; 32 | } 33 | 34 | // "0001234" -(trim)-> "1234" -(int)-> 1234 -(string)-> "1234" 35 | // ^^ are the same ^^ 36 | $str = Str\trim_left($str, '0'); 37 | if ($str === (string)$int) { 38 | return $int; 39 | } 40 | 41 | // Exceptional case "000" -(trim)-> "", but we want to return 0 42 | if ($str === '' && $value !== '') { 43 | return 0; 44 | } 45 | } 46 | throw TypeCoercionException::withValue($this->getTrace(), 'int', $value); 47 | } 48 | 49 | <<__Override>> 50 | public function assertType(mixed $value): int { 51 | if ($value is int) { 52 | return $value; 53 | } 54 | throw IncorrectTypeException::withValue($this->getTrace(), 'int', $value); 55 | } 56 | 57 | <<__Override>> 58 | public function toString(): string { 59 | return 'int'; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/TypeSpec/__Private/KeyedTraversableSpec.hack: -------------------------------------------------------------------------------- 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, UnsupportedTypeException}; 14 | use type Facebook\TypeSpec\TypeSpec; 15 | use namespace HH\Lib\Str; 16 | 17 | final class KeyedTraversableSpec> 18 | extends TypeSpec { 19 | use NoCoercionSpecTrait; 20 | 21 | public function __construct( 22 | private classname $outer, 23 | private TypeSpec $tsk, 24 | private TypeSpec $tsv, 25 | ) { 26 | } 27 | 28 | <<__Override>> 29 | public function assertType(mixed $value): T { 30 | // Switch is needed as values such as PHP arrays pass instanceof, but not is_a() 31 | switch ($this->outer) { 32 | case KeyedContainer::class: 33 | $valid_outer = $value is KeyedContainer<_, _>; 34 | break; 35 | case KeyedTraversable::class: 36 | $valid_outer = $value is KeyedTraversable<_, _>; 37 | break; 38 | default: 39 | $valid_outer = \is_a($value, $this->outer); 40 | } 41 | 42 | if (!$valid_outer) { 43 | throw IncorrectTypeException::withValue( 44 | $this->getTrace(), 45 | $this->outer.'', 46 | $value, 47 | ); 48 | } 49 | 50 | invariant( 51 | $value is KeyedTraversable<_, _>, 52 | 'expected KeyedTraversable, got %s', 53 | \is_object($value) ? \get_class($value) : \gettype($value), 54 | ); 55 | 56 | // Non-Container traversables may not be rewindable, e.g. generators, so 57 | // we can't check the values. 58 | // 59 | // Iterator::rewind() must exist, but may be a no-op, so we can't trust it. 60 | if (!$value is KeyedContainer<_, _>) { 61 | throw new UnsupportedTypeException( 62 | 'non-KeyedContainer KeyedTraversable '.\get_class($value), 63 | ); 64 | } 65 | 66 | $key_trace = $this->getTrace()->withFrame($this->outer.''); 67 | $value_trace = $this->getTrace()->withFrame($this->outer.'<_, Tv>'); 68 | $tsk = $this->tsk->withTrace($key_trace); 69 | $tsv = $this->tsv->withTrace($value_trace); 70 | foreach ($value as $k => $v) { 71 | $tsk->assertType($k); 72 | $tsv->assertType($v); 73 | } 74 | return /* HH_IGNORE_ERROR[4110] */ $value; 75 | } 76 | 77 | <<__Override>> 78 | public function toString(): string { 79 | return Str\format( 80 | '%s<%s, %s>', 81 | KeyedTraversable::class, 82 | $this->tsk->toString(), 83 | $this->tsv->toString(), 84 | ); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/TypeSpec/__Private/KeysetSpec.hack: -------------------------------------------------------------------------------- 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, TypeCoercionException}; 14 | use type Facebook\TypeSpec\TypeSpec; 15 | use namespace HH\Lib\Keyset; 16 | 17 | final class KeysetSpec extends TypeSpec> { 18 | public function __construct(private TypeSpec $inner) { 19 | } 20 | 21 | <<__Override>> 22 | public function coerceType(mixed $value): keyset { 23 | if (!$value is Traversable<_>) { 24 | throw TypeCoercionException::withValue( 25 | $this->getTrace(), 26 | 'keyset', 27 | $value, 28 | ); 29 | } 30 | 31 | $trace = $this->getTrace()->withFrame('keyset'); 32 | 33 | return Keyset\map( 34 | $value, 35 | $inner ==> $this->inner->withTrace($trace)->coerceType($inner), 36 | ); 37 | } 38 | 39 | <<__Override>> 40 | public function assertType(mixed $value): keyset { 41 | if (!($value is keyset<_>)) { 42 | throw IncorrectTypeException::withValue( 43 | $this->getTrace(), 44 | 'keyset', 45 | $value, 46 | ); 47 | } 48 | 49 | $trace = $this->getTrace()->withFrame('keyset'); 50 | 51 | return Keyset\map( 52 | $value, 53 | $inner ==> $this->inner->withTrace($trace)->assertType($inner), 54 | ); 55 | } 56 | 57 | <<__Override>> 58 | public function toString(): string { 59 | return keyset::class.'<'.$this->inner->toString().'>'; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/TypeSpec/__Private/MapSpec.hack: -------------------------------------------------------------------------------- 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, TypeCoercionException}; 14 | use type Facebook\TypeSpec\TypeSpec; 15 | use namespace HH\Lib\{C, Str}; 16 | 17 | final class MapSpec> 18 | extends TypeSpec { 19 | 20 | public function __construct( 21 | private classname $what, 22 | private TypeSpec $tsk, 23 | private TypeSpec $tsv, 24 | ) { 25 | $valid = keyset[Map::class, ImmMap::class, \ConstMap::class]; 26 | invariant( 27 | C\contains_key($valid, $what), 28 | 'Only built-in \ConstMap implementations are supported', 29 | ); 30 | } 31 | 32 | <<__Override>> 33 | public function coerceType(mixed $value): T { 34 | if (!$value is KeyedTraversable<_, _>) { 35 | throw TypeCoercionException::withValue( 36 | $this->getTrace(), 37 | $this->what, 38 | $value, 39 | ); 40 | } 41 | 42 | $kt = $this->getTrace()->withFrame($this->what.''); 43 | $vt = $this->getTrace()->withFrame($this->what.'<_, Tv>'); 44 | 45 | $tsk = $this->tsk->withTrace($kt); 46 | $tsv = $this->tsv->withTrace($vt); 47 | 48 | $out = Map {}; 49 | $changed = false; 50 | foreach ($value as $k => $v) { 51 | $kk = $tsk->coerceType($k); 52 | $vv = $tsv->coerceType($v); 53 | $out[$kk] = $vv; 54 | $changed = $changed || ($kk !== $k) || ($vv !== $v); 55 | } 56 | 57 | if ( 58 | $changed === false && 59 | \is_a($value, $this->what, /* allow_string = */ true) 60 | ) { 61 | /* HH_IGNORE_ERROR[4110] */ 62 | return $value; 63 | } 64 | 65 | if ($this->what === Map::class) { 66 | /* HH_IGNORE_ERROR[4110] */ 67 | return $out; 68 | } 69 | 70 | /* HH_IGNORE_ERROR[4110] */ 71 | return $out->immutable(); 72 | } 73 | 74 | <<__Override>> 75 | public function assertType(mixed $value): T { 76 | if (!\is_a($value, $this->what)) { 77 | throw IncorrectTypeException::withValue( 78 | $this->getTrace(), 79 | $this->what, 80 | $value, 81 | ); 82 | } 83 | $value as \ConstMap<_, _>; 84 | $out = Map {}; 85 | $out->reserve($value->count()); 86 | 87 | $key_spec = $this->tsk 88 | ->withTrace($this->getTrace()->withFrame($this->what.'')); 89 | $value_spec = $this->tsv 90 | ->withTrace($this->getTrace()->withFrame($this->what.'<_, Tv>')); 91 | 92 | // EnumSpec may change its values, and can be nested here 93 | $changed = false; 94 | foreach ($value as $key => $element) { 95 | $new_key = $key_spec->assertType($key); 96 | $new_element = $value_spec->assertType($element); 97 | $changed = $changed || $new_key !== $key || $new_element !== $element; 98 | $out[$new_key] = $new_element; 99 | } 100 | 101 | if (!$changed) { 102 | /* HH_IGNORE_ERROR[4110] is_a() ensure the collection type 103 | and $spec->assertType() ensures the inner type. */ 104 | return $value; 105 | } 106 | 107 | if ($this->what === Map::class) { 108 | /* HH_IGNORE_ERROR[4110] $out is a Map and $this->what is also Map. */ 109 | return $out; 110 | } 111 | 112 | /* HH_IGNORE_ERROR[4110] Return ImmMap when the user asks for ConstMap or ImmMap. 113 | This immutability for ConstMap is not needed, but kept for backwards compatibility. */ 114 | return $out->immutable(); 115 | } 116 | 117 | <<__Override>> 118 | public function toString(): string { 119 | return Str\format( 120 | '%s<%s, %s>', 121 | $this->what, 122 | $this->tsk->toString(), 123 | $this->tsv->toString(), 124 | ); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/TypeSpec/__Private/MixedSpec.hack: -------------------------------------------------------------------------------- 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\TypeSpec\TypeSpec; 14 | 15 | final class MixedSpec extends TypeSpec { 16 | <<__Override>> 17 | public function coerceType(mixed $value): mixed { 18 | return $value; 19 | } 20 | 21 | <<__Override>> 22 | public function assertType(mixed $value): mixed { 23 | return $value; 24 | } 25 | 26 | <<__Override>> 27 | public function toString(): string { 28 | return 'mixed'; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/TypeSpec/__Private/NoCoercionSpecTrait.hack: -------------------------------------------------------------------------------- 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, TypeCoercionException}; 14 | 15 | trait NoCoercionSpecTrait { 16 | require extends \Facebook\TypeSpec\TypeSpec; 17 | 18 | final public function coerceType(mixed $value): T { 19 | try { 20 | return $this->assertType($value); 21 | } catch (IncorrectTypeException $e) { 22 | throw TypeCoercionException::withValue( 23 | $this->getTrace(), 24 | $e->getExpectedType(), 25 | $value, 26 | ); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/TypeSpec/__Private/NonNullSpec.hack: -------------------------------------------------------------------------------- 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 { 17 | use NoCoercionSpecTrait; 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 | } 36 | -------------------------------------------------------------------------------- /src/TypeSpec/__Private/NullSpec.hack: -------------------------------------------------------------------------------- 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 { 17 | use NoCoercionSpecTrait; 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 | } 36 | -------------------------------------------------------------------------------- /src/TypeSpec/__Private/NullableSpec.hack: -------------------------------------------------------------------------------- 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, TypeCoercionException}; 14 | use type Facebook\TypeSpec\TypeSpec; 15 | 16 | final class NullableSpec extends TypeSpec { 17 | public function __construct(private TypeSpec $inner) { 18 | invariant( 19 | !$inner is OptionalSpec<_>, 20 | 'OptionalSpec should be the outermost spec', 21 | ); 22 | } 23 | 24 | <<__Override>> 25 | public function coerceType(mixed $value): ?T { 26 | if ($value === null) { 27 | return null; 28 | } 29 | try { 30 | return $this->inner->coerceType($value); 31 | } catch (TypeCoercionException $e) { 32 | throw new TypeCoercionException( 33 | $this->getTrace(), 34 | '?'.$e->getTargetType(), 35 | $e->getActualType(), 36 | ); 37 | } 38 | } 39 | 40 | <<__Override>> 41 | public function assertType(mixed $value): ?T { 42 | if ($value === null) { 43 | return null; 44 | } 45 | try { 46 | return $this->inner->assertType($value); 47 | } catch (IncorrectTypeException $e) { 48 | throw new IncorrectTypeException( 49 | $this->getTrace(), 50 | '?'.$e->getExpectedType(), 51 | $e->getActualType(), 52 | ); 53 | } 54 | } 55 | 56 | <<__Override>> 57 | public function toString(): string { 58 | return '?'.$this->inner->toString(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/TypeSpec/__Private/NumSpec.hack: -------------------------------------------------------------------------------- 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 | final class NumSpec extends UnionSpec { 14 | public function __construct() { 15 | parent::__construct('num', new IntSpec(), new FloatSpec()); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/TypeSpec/__Private/OptionalSpec.hack: -------------------------------------------------------------------------------- 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\TypeSpec\TypeSpec; 14 | 15 | final class OptionalSpec extends TypeSpec { 16 | public function __construct(private TypeSpec $inner) { 17 | } 18 | 19 | <<__Override>> 20 | public function isOptional(): bool { 21 | return true; 22 | } 23 | 24 | <<__Override>> 25 | public function coerceType(mixed $value): T { 26 | return $this->inner->withTrace($this->getTrace())->coerceType($value); 27 | } 28 | 29 | <<__Override>> 30 | public function assertType(mixed $value): T { 31 | return $this->inner->withTrace($this->getTrace())->assertType($value); 32 | } 33 | 34 | <<__Override>> 35 | public function toString(): string { 36 | return $this->inner->toString(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/TypeSpec/__Private/ResourceSpec.hack: -------------------------------------------------------------------------------- 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 ResourceSpec extends TypeSpec { 17 | public function __construct(private ?string $kind = null) { 18 | } 19 | use NoCoercionSpecTrait; 20 | 21 | <<__Override>> 22 | public function assertType(mixed $value): resource { 23 | if (!($value is resource)) { 24 | throw IncorrectTypeException::withValue( 25 | $this->getTrace(), 26 | $this->getPrettyType(), 27 | $value, 28 | ); 29 | } 30 | 31 | $kind = $this->kind; 32 | if ($kind !== null && \get_resource_type($value) !== $kind) { 33 | throw IncorrectTypeException::withValue( 34 | $this->getTrace(), 35 | $this->getPrettyType(), 36 | $value, 37 | ); 38 | } 39 | 40 | return $value; 41 | } 42 | 43 | private function getPrettyType(): string { 44 | $kind = $this->kind; 45 | if ($kind === null) { 46 | return 'resource'; 47 | } 48 | return 'resource<'.$kind.'>'; 49 | } 50 | 51 | <<__Override>> 52 | public function toString(): string { 53 | return 'resource'; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/TypeSpec/__Private/SetSpec.hack: -------------------------------------------------------------------------------- 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, TypeCoercionException}; 14 | use type Facebook\TypeSpec\TypeSpec; 15 | use namespace HH\Lib\C; 16 | 17 | final class SetSpec> extends TypeSpec { 18 | public function __construct( 19 | private classname $what, 20 | private TypeSpec $inner, 21 | ) { 22 | $valid = keyset[Set::class, ImmSet::class, \ConstSet::class]; 23 | invariant( 24 | C\contains_key($valid, $what), 25 | 'Only built-in \ConstSet implementations are supported', 26 | ); 27 | } 28 | 29 | <<__Override>> 30 | public function coerceType(mixed $value): T { 31 | if (!$value is Traversable<_>) { 32 | throw TypeCoercionException::withValue( 33 | $this->getTrace(), 34 | $this->what, 35 | $value, 36 | ); 37 | } 38 | 39 | $trace = $this->getTrace()->withFrame($this->what.''); 40 | $ts = $this->inner->withTrace($trace); 41 | $changed = false; 42 | $out = Set {}; 43 | foreach ($value as $v) { 44 | $vv = $ts->coerceType($v); 45 | $changed = $changed || ($vv !== $v); 46 | $out[] = $vv; 47 | } 48 | 49 | if ($changed === false && \is_a($value, $this->what)) { 50 | /* HH_IGNORE_ERROR[4110] */ 51 | return $value; 52 | } 53 | 54 | if ($this->what === Set::class) { 55 | /* HH_IGNORE_ERROR[4110] */ 56 | return $out; 57 | } 58 | /* HH_IGNORE_ERROR[4110] */ 59 | return $out->immutable(); 60 | } 61 | 62 | <<__Override>> 63 | public function assertType(mixed $value): T { 64 | if (!\is_a($value, $this->what)) { 65 | throw IncorrectTypeException::withValue( 66 | $this->getTrace(), 67 | $this->what, 68 | $value, 69 | ); 70 | } 71 | 72 | $value as \ConstSet<_>; 73 | $out = Set {}; 74 | $out->reserve($value->count()); 75 | 76 | $spec = 77 | $this->inner->withTrace($this->getTrace()->withFrame($this->what.'')); 78 | 79 | // EnumSpec may change its values, and can be nested here 80 | $changed = false; 81 | foreach ($value as $element) { 82 | $new_element = $spec->assertType($element); 83 | $changed = $changed || $new_element !== $element; 84 | $out[] = $new_element; 85 | } 86 | 87 | if (!$changed) { 88 | /* HH_IGNORE_ERROR[4110] is_a() ensures the collection type 89 | and $spec->assertType() ensures the inner type. */ 90 | return $value; 91 | } 92 | 93 | if ($this->what === Set::class) { 94 | /* HH_IGNORE_ERROR[4110] $out is a Set and $this->what is also Set. */ 95 | return $out; 96 | } 97 | 98 | /* HH_IGNORE_ERROR[4110] Return ImmSet when the user asks for ConstSet or ImmSet. 99 | This immutability for ConstSet is not needed, but kept for consistency with MapSpec and VectorSpec. */ 100 | return $out->immutable(); 101 | } 102 | 103 | <<__Override>> 104 | public function toString(): string { 105 | return $this->what.'<'.$this->inner->toString().'>'; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/TypeSpec/__Private/ShapeSpec.hack: -------------------------------------------------------------------------------- 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, TypeCoercionException}; 14 | use type Facebook\TypeSpec\TypeSpec; 15 | use namespace HH\Lib\{C, Dict, Str, Vec}; 16 | 17 | final class ShapeSpec extends TypeSpec { 18 | private bool $allowUnknownFields; 19 | 20 | private static function isOptionalField(TypeSpec $spec): bool { 21 | return $spec->isOptional(); 22 | } 23 | 24 | public function __construct( 25 | private dict> $inners, 26 | UnknownFieldsMode $unknown_fields, 27 | ) { 28 | $this->allowUnknownFields = $unknown_fields === UnknownFieldsMode::ALLOW; 29 | } 30 | 31 | <<__Override>> 32 | public function coerceType(mixed $value): shape() { 33 | if (!$value is KeyedTraversable<_, _>) { 34 | throw TypeCoercionException::withValue( 35 | $this->getTrace(), 36 | 'shape', 37 | $value, 38 | ); 39 | } 40 | 41 | $value = dict(/* HH_IGNORE_ERROR[4323] */$value); 42 | $out = dict[]; 43 | foreach ($this->inners as $key => $spec) { 44 | $trace = $this->getTrace()->withFrame('shape['.$key.']'); 45 | if (C\contains_key($value, $key)) { 46 | $out[$key] = $spec->withTrace($trace)->coerceType($value[$key] ?? null); 47 | continue; 48 | } 49 | 50 | if (self::isOptionalField($spec)) { 51 | continue; 52 | } 53 | 54 | throw new TypeCoercionException($trace, 'value', 'missing shape field'); 55 | } 56 | 57 | if ($this->allowUnknownFields) { 58 | foreach ($value as $k => $v) { 59 | if (!C\contains_key($out, $k)) { 60 | $out[$k] = $v; 61 | } 62 | } 63 | } 64 | 65 | return self::dictToShapeUNSAFE($out); 66 | } 67 | 68 | <<__Override>> 69 | public function assertType(mixed $value): shape() { 70 | if (!\HH\is_dict_or_darray($value)) { 71 | throw IncorrectTypeException::withValue( 72 | $this->getTrace(), 73 | 'shape', 74 | $value, 75 | ); 76 | } 77 | $out = dict[]; 78 | foreach ($this->inners as $key => $spec) { 79 | $trace = $this->getTrace()->withFrame('shape['.$key.']'); 80 | if (C\contains_key($value, $key)) { 81 | $out[$key as arraykey] = $spec->withTrace($trace)->assertType($value[$key as dynamic] ?? null); 82 | continue; 83 | } 84 | 85 | if (self::isOptionalField($spec)) { 86 | continue; 87 | } 88 | 89 | throw new IncorrectTypeException( 90 | $trace, 91 | 'value', 92 | 'missing shape field ("'.$key.'")', 93 | ); 94 | } 95 | foreach ($value as $k => $v) { 96 | if (!C\contains_key($out, $k)) { 97 | if ($this->allowUnknownFields) { 98 | $out[$k] = $v; 99 | } else { 100 | throw IncorrectTypeException::withValue( 101 | $this->getTrace()->withFrame('shape['.$k.']'), 102 | 'no extra shape fields', 103 | $v, 104 | ); 105 | } 106 | } 107 | } 108 | 109 | return self::dictToShapeUNSAFE($out); 110 | } 111 | 112 | private static function dictToShapeUNSAFE( 113 | dict $shape, 114 | ): shape() { 115 | if (shape() is dict<_, _>) { 116 | /* HH_IGNORE_ERROR[4110] */ 117 | return $shape; 118 | } 119 | return /* HH_IGNORE_ERROR[4110] */ darray($shape); 120 | } 121 | 122 | <<__Override>> 123 | public function toString(): string { 124 | return $this->inners 125 | |> Dict\map_with_key( 126 | $$, 127 | ($name, $spec) ==> Str\format( 128 | " %s'%s' => %s,", 129 | $spec->isOptional() ? '?' : '', 130 | $name, 131 | $spec->toString(), 132 | ), 133 | ) 134 | |> $this->allowUnknownFields ? Vec\concat($$, vec[' ...']) : $$ 135 | |> Str\join($$, "\n") 136 | |> "shape(\n".$$."\n)"; 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/TypeSpec/__Private/StringSpec.hack: -------------------------------------------------------------------------------- 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, TypeCoercionException}; 14 | use type Facebook\TypeSpec\TypeSpec; 15 | 16 | final class StringSpec extends TypeSpec { 17 | <<__Override>> 18 | public function coerceType(mixed $value): string { 19 | if ($value is string) { 20 | return $value; 21 | } 22 | if ($value is \Stringish) { 23 | return stringish_cast($value, __CLASS__.'::'.__METHOD__); 24 | } 25 | if ($value is int) { 26 | return (string)$value; 27 | } 28 | throw TypeCoercionException::withValue($this->getTrace(), 'string', $value); 29 | } 30 | 31 | <<__Override>> 32 | public function assertType(mixed $value): string { 33 | if ($value is string) { 34 | return $value; 35 | } 36 | throw new IncorrectTypeException( 37 | $this->getTrace(), 38 | 'string', 39 | \gettype($value), 40 | ); 41 | } 42 | 43 | <<__Override>> 44 | public function toString(): string { 45 | return 'string'; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/TypeSpec/__Private/TraversableSpec.hack: -------------------------------------------------------------------------------- 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, UnsupportedTypeException}; 14 | use type Facebook\TypeSpec\TypeSpec; 15 | 16 | final class TraversableSpec> 17 | extends TypeSpec { 18 | use NoCoercionSpecTrait; 19 | 20 | public function __construct( 21 | private classname $outer, 22 | private TypeSpec $inner, 23 | ) { 24 | } 25 | 26 | <<__Override>> 27 | public function assertType(mixed $value): T { 28 | $frame = $this->outer.''; 29 | 30 | // Switch is needed as values such as PHP arrays pass instanceof, but not is_a() 31 | switch ($this->outer) { 32 | case Container::class: 33 | $valid_outer = $value is Container<_>; 34 | break; 35 | case Traversable::class: 36 | $valid_outer = $value is Traversable<_>; 37 | break; 38 | default: 39 | $valid_outer = \is_a($value, $this->outer); 40 | } 41 | 42 | if (!$valid_outer) { 43 | throw IncorrectTypeException::withValue( 44 | $this->getTrace(), 45 | $frame, 46 | $value, 47 | ); 48 | } 49 | 50 | invariant( 51 | $value is Traversable<_>, 52 | 'expected Traversable, got %s', 53 | \is_object($value) ? \get_class($value) : \gettype($value), 54 | ); 55 | 56 | // Non-Container traversables may not be rewindable, e.g. generators, so 57 | // we can't check the values. 58 | // 59 | // Iterator::rewind() must exist, but may be a no-op, so we can't trust it. 60 | if (!$value is Container<_>) { 61 | throw new UnsupportedTypeException( 62 | 'non-Container Traversable '.\get_class($value), 63 | ); 64 | } 65 | 66 | $trace = $this->getTrace()->withFrame($frame); 67 | foreach ($value as $v) { 68 | $this->inner->withTrace($trace)->assertType($v); 69 | } 70 | return /* HH_IGNORE_ERROR[4110] */ $value; 71 | } 72 | 73 | <<__Override>> 74 | public function toString(): string { 75 | return Traversable::class.'<'.$this->inner->toString().'>'; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/TypeSpec/__Private/TupleSpec.hack: -------------------------------------------------------------------------------- 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, TypeCoercionException}; 14 | use type Facebook\TypeSpec\TypeSpec; 15 | use namespace HH\Lib\{Str, Vec}; 16 | 17 | newtype BogusTuple = (mixed, mixed); 18 | 19 | final class TupleSpec extends TypeSpec { 20 | public function __construct(private vec> $inners) { 21 | } 22 | 23 | <<__Override>> 24 | public function coerceType(mixed $value): BogusTuple { 25 | if (!\HH\is_vec_or_varray($value)) { 26 | throw 27 | TypeCoercionException::withValue($this->getTrace(), 'tuple', $value); 28 | } 29 | $values = vec($value); 30 | 31 | $count = \count($values); 32 | if ($count !== \count($this->inners)) { 33 | throw TypeCoercionException::withValue( 34 | $this->getTrace(), 35 | \count($this->inners).'-tuple', 36 | $value, 37 | ); 38 | } 39 | 40 | $out = vec[]; 41 | for ($i = 0; $i < $count; ++$i) { 42 | $out[] = $this->inners[$i] 43 | ->withTrace($this->getTrace()->withFrame('tuple['.$i.']')) 44 | ->coerceType($values[$i]); 45 | } 46 | return self::vecToTuple($out); 47 | } 48 | 49 | <<__Override>> 50 | public function assertType(mixed $value): BogusTuple { 51 | if (\HH\is_vec_or_varray($value)) { 52 | $value = vec($value); 53 | } else if (!($value is vec<_>)) { 54 | throw 55 | IncorrectTypeException::withValue($this->getTrace(), 'tuple', $value); 56 | } 57 | $values = $value; 58 | 59 | $count = \count($values); 60 | if ($count !== \count($this->inners)) { 61 | throw IncorrectTypeException::withValue( 62 | $this->getTrace(), 63 | \count($this->inners).'-tuple', 64 | $value, 65 | ); 66 | } 67 | 68 | $out = vec[]; 69 | for ($i = 0; $i < $count; ++$i) { 70 | $out[] = $this->inners[$i] 71 | ->withTrace($this->getTrace()->withFrame('tuple['.$i.']')) 72 | ->assertType($values[$i]); 73 | } 74 | return self::vecToTuple($out); 75 | } 76 | 77 | private static function vecToTuple(vec $tuple): BogusTuple { 78 | if (tuple('foo') is vec<_>) { 79 | /* HH_IGNORE_ERROR[4110] */ 80 | return $tuple; 81 | } 82 | /* HH_IGNORE_ERROR[4110] */ 83 | return varray($tuple); 84 | } 85 | 86 | <<__Override>> 87 | public function toString(): string { 88 | return Vec\map($this->inners, $it ==> $it->toString()) 89 | |> Str\join($$, ', ') 90 | |> '('.$$.')'; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/TypeSpec/__Private/UnionSpec.hack: -------------------------------------------------------------------------------- 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, TypeCoercionException}; 14 | use type Facebook\TypeSpec\TypeSpec; 15 | 16 | abstract class UnionSpec<+T> extends TypeSpec { 17 | private vec> $inners; 18 | public function __construct(private string $name, TypeSpec ...$inners) { 19 | $this->inners = vec($inners); 20 | } 21 | 22 | <<__Override>> 23 | public function coerceType(mixed $value): T { 24 | try { 25 | return $this->assertType($value); 26 | } catch (IncorrectTypeException $_) { 27 | // try coercion 28 | } 29 | foreach ($this->inners as $spec) { 30 | try { 31 | return $spec->coerceType($value); 32 | } catch (TypeCoercionException $_) { 33 | // try next 34 | } 35 | } 36 | throw TypeCoercionException::withValue( 37 | $this->getTrace(), 38 | $this->name, 39 | $value, 40 | ); 41 | } 42 | 43 | <<__Override>> 44 | final public function assertType(mixed $value): T { 45 | foreach ($this->inners as $spec) { 46 | try { 47 | return $spec->assertType($value); 48 | } catch (IncorrectTypeException $_) { 49 | // try next 50 | } 51 | } 52 | throw IncorrectTypeException::withValue( 53 | $this->getTrace(), 54 | $this->name, 55 | $value, 56 | ); 57 | } 58 | 59 | <<__Override>> 60 | public function toString(): string { 61 | return $this->name; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/TypeSpec/__Private/UnknownFieldsMode.hack: -------------------------------------------------------------------------------- 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 | enum UnknownFieldsMode : string { 14 | ALLOW = 'allow'; 15 | DENY = 'deny'; 16 | } 17 | -------------------------------------------------------------------------------- /src/TypeSpec/__Private/UntypedArraySpec.hack: -------------------------------------------------------------------------------- 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, TypeCoercionException}; 14 | use type Facebook\TypeSpec\TypeSpec; 15 | 16 | final class UntypedArraySpec extends TypeSpec> { 17 | 18 | <<__Override>> 19 | public function coerceType(mixed $value): varray_or_darray { 20 | if (!$value is KeyedTraversable<_, _>) { 21 | throw 22 | TypeCoercionException::withValue($this->getTrace(), 'array', $value); 23 | } 24 | 25 | $out = darray[]; 26 | foreach ($value as $k => $v) { 27 | $out[$k as arraykey] = $v; 28 | } 29 | return $out; 30 | } 31 | 32 | <<__Override>> 33 | public function assertType(mixed $value): varray_or_darray { 34 | if (!\HH\is_php_array($value)) { 35 | throw 36 | IncorrectTypeException::withValue($this->getTrace(), 'array', $value); 37 | } 38 | 39 | if (varray($value) === $value) { 40 | return varray($value); 41 | } 42 | return darray($value); 43 | } 44 | 45 | <<__Override>> 46 | public function toString(): string { 47 | return 'varray_or_darray'; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/TypeSpec/__Private/VArrayOrDArraySpec.hack: -------------------------------------------------------------------------------- 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 namespace HH\Lib\{C, Vec}; 14 | use namespace Facebook\TypeSpec; 15 | 16 | final class VArrayOrDArraySpec extends UnionSpec> { 17 | private TypeSpec\TypeSpec> $darraySpec; 18 | private TypeSpec\TypeSpec> $varraySpec; 19 | 20 | public function __construct(private TypeSpec\TypeSpec $inner) { 21 | $this->darraySpec = TypeSpec\darray(TypeSpec\arraykey(), $inner); 22 | $this->varraySpec = TypeSpec\varray($inner); 23 | parent::__construct( 24 | 'varray_or_darray', 25 | $this->darraySpec, 26 | $this->varraySpec, 27 | ); 28 | } 29 | 30 | <<__Override>> 31 | public function toString(): string { 32 | return 'varray_or_darray<'.$this->inner->toString().'>'; 33 | } 34 | 35 | <<__Override>> 36 | public function coerceType(mixed $value): varray_or_darray { 37 | try { 38 | return $this->assertType($value); 39 | } catch (\Throwable $_) { 40 | } 41 | 42 | if ($value is vec<_> || $value is /* HH_FIXME[2049] */ ConstVector<_>) { 43 | return $this->varraySpec->coerceType($value); 44 | } 45 | 46 | if ($value is dict<_, _> || $value is /* HH_FIXME[2049] */ ConstMap<_, _>) { 47 | return $this->darraySpec->coerceType($value); 48 | } 49 | 50 | $new = $this->darraySpec->coerceType($value); 51 | if ($new === darray[]) { 52 | return $new; 53 | } 54 | 55 | if (Vec\keys($new) === Vec\range(0, C\count($new) - 1)) { 56 | return varray($new); 57 | } 58 | return $new; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/TypeSpec/__Private/VarraySpec.hack: -------------------------------------------------------------------------------- 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, TypeCoercionException}; 14 | use type Facebook\TypeSpec\TypeSpec; 15 | use namespace HH\Lib\Vec; 16 | 17 | final class VarraySpec extends TypeSpec> { 18 | public function __construct( 19 | private TypeSpec $inner, 20 | ) { 21 | } 22 | 23 | <<__Override>> 24 | public function coerceType(mixed $value): varray { 25 | if (!$value is Traversable<_>) { 26 | throw TypeCoercionException::withValue( 27 | $this->getTrace(), 28 | 'varray', 29 | $value, 30 | ); 31 | } 32 | 33 | return Vec\map($value, $inner ==> $this->inner->coerceType($inner)) 34 | |> varray($$); 35 | } 36 | 37 | <<__Override>> 38 | public function assertType(mixed $value): varray { 39 | if (/* HH_FIXME[2049] */ /* HH_FIXME[4107] */ !is_varray($value)) { 40 | throw IncorrectTypeException::withValue( 41 | $this->getTrace(), 42 | 'varray', 43 | $value, 44 | ); 45 | } 46 | 47 | $counter = ( 48 | (): \Generator ==> { 49 | for ($i = 0; true; $i++) { 50 | yield $i; 51 | } 52 | } 53 | )(); 54 | 55 | return Vec\map_with_key( 56 | $value as KeyedTraversable<_, _>, 57 | ($k, $inner) ==> { 58 | $counter->next(); 59 | $i = $counter->current(); 60 | if ($k !== $i) { 61 | throw IncorrectTypeException::withValue( 62 | $this->getTrace(), 63 | 'key '.$i, 64 | $k, 65 | ); 66 | } 67 | return $this 68 | ->inner 69 | ->withTrace($this->getTrace()->withFrame('varray['.$i.']')) 70 | ->assertType($inner); 71 | }, 72 | ) 73 | |> varray($$); 74 | } 75 | 76 | <<__Override>> 77 | public function toString(): string { 78 | return 'varray<'.$this->inner->toString().'>'; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/TypeSpec/__Private/VecSpec.hack: -------------------------------------------------------------------------------- 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, TypeCoercionException}; 14 | use type Facebook\TypeSpec\TypeSpec; 15 | use namespace HH\Lib\Vec; 16 | 17 | final class VecSpec extends TypeSpec> { 18 | public function __construct(private TypeSpec $inner) { 19 | } 20 | 21 | <<__Override>> 22 | public function coerceType(mixed $value): vec { 23 | if (!$value is Traversable<_>) { 24 | throw TypeCoercionException::withValue( 25 | $this->getTrace(), 26 | 'vec', 27 | $value, 28 | ); 29 | } 30 | 31 | $trace = $this->getTrace()->withFrame('vec'); 32 | return Vec\map( 33 | $value, 34 | $inner ==> $this->inner->withTrace($trace)->coerceType($inner), 35 | ); 36 | } 37 | 38 | <<__Override>> 39 | public function assertType(mixed $value): vec { 40 | if (!($value is vec<_>)) { 41 | throw IncorrectTypeException::withValue( 42 | $this->getTrace(), 43 | 'vec', 44 | $value, 45 | ); 46 | } 47 | 48 | $trace = $this->getTrace()->withFrame('vec'); 49 | return Vec\map( 50 | $value, 51 | $inner ==> $this->inner->withTrace($trace)->assertType($inner), 52 | ); 53 | } 54 | 55 | <<__Override>> 56 | public function toString(): string { 57 | return vec::class.'<'.$this->inner->toString().'>'; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/TypeSpec/__Private/VectorSpec.hack: -------------------------------------------------------------------------------- 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, TypeCoercionException}; 14 | use type Facebook\TypeSpec\TypeSpec; 15 | use namespace HH\Lib\C; 16 | 17 | final class VectorSpec> extends TypeSpec { 18 | public function __construct( 19 | private classname $what, 20 | private TypeSpec $inner, 21 | ) { 22 | $valid = keyset[Vector::class, ImmVector::class, \ConstVector::class]; 23 | invariant( 24 | C\contains_key($valid, $what), 25 | 'Only built-in \ConstVector implementations are supported', 26 | ); 27 | } 28 | 29 | <<__Override>> 30 | public function coerceType(mixed $value): T { 31 | if (!$value is Traversable<_>) { 32 | throw TypeCoercionException::withValue( 33 | $this->getTrace(), 34 | $this->what, 35 | $value, 36 | ); 37 | } 38 | 39 | $trace = $this->getTrace()->withFrame($this->what.''); 40 | $ts = $this->inner->withTrace($trace); 41 | 42 | $out = Vector {}; 43 | $changed = false; 44 | foreach ($value as $v) { 45 | $vv = $ts->coerceType($v); 46 | $changed = $changed || ($v !== $vv); 47 | $out[] = $vv; 48 | } 49 | 50 | if ($changed === false && \is_a($value, $this->what)) { 51 | return /* HH_IGNORE_ERROR[4110] */ $value; 52 | } 53 | 54 | if ($this->what === Vector::class) { 55 | return /* HH_IGNORE_ERROR[4110] */ $out; 56 | } 57 | 58 | return /* HH_IGNORE_ERROR[4110] */ $out->immutable(); 59 | } 60 | 61 | <<__Override>> 62 | public function assertType(mixed $value): T { 63 | if (!\is_a($value, $this->what)) { 64 | throw IncorrectTypeException::withValue( 65 | $this->getTrace(), 66 | $this->what, 67 | $value, 68 | ); 69 | } 70 | $value as \ConstVector<_>; 71 | 72 | $out = Vector {}; 73 | $out->reserve($value->count()); 74 | 75 | $spec = 76 | $this->inner->withTrace($this->getTrace()->withFrame($this->what.'')); 77 | 78 | // EnumSpec may change its values, and can be nested here 79 | $changed = false; 80 | foreach ($value as $element) { 81 | $new_element = $spec->assertType($element); 82 | $changed = $changed || $new_element !== $element; 83 | $out[] = $new_element; 84 | } 85 | 86 | if (!$changed) { 87 | /* HH_IGNORE_ERROR[4110] is_a() ensure the collection type 88 | and $spec->assertType() ensures the inner type. */ 89 | return $value; 90 | } 91 | 92 | if ($this->what === Vector::class) { 93 | /* HH_IGNORE_ERROR[4110] $out is a Vector and $this->what is also Vector. */ 94 | return $out; 95 | } 96 | 97 | /* HH_IGNORE_ERROR[4110] Return ImmVector when the user asks for ConstVector or ImmVector. 98 | This immutability for ConstVector is not needed, but kept for backwards compatibility. */ 99 | return $out->immutable(); 100 | } 101 | 102 | <<__Override>> 103 | public function toString(): string { 104 | return $this->what.'<'.$this->inner->toString().'>'; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/TypeSpec/__Private/from_type_structure.hack: -------------------------------------------------------------------------------- 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\UnsupportedTypeException; 14 | use type Facebook\TypeSpec\TypeSpec; 15 | use namespace HH\Lib\{C, Dict, Vec}; 16 | use namespace Facebook\{TypeAssert, TypeSpec}; 17 | 18 | function from_type_structure(TypeStructure $ts): TypeSpec { 19 | if ($ts['optional_shape_field'] ?? false) { 20 | $ts['optional_shape_field'] = false; 21 | /* HH_IGNORE_ERROR[4110] */ 22 | return new OptionalSpec(from_type_structure($ts)); 23 | } 24 | if ($ts['nullable'] ?? false) { 25 | $ts['nullable'] = false; 26 | /* HH_IGNORE_ERROR[4110] */ 27 | return new NullableSpec(from_type_structure($ts)); 28 | } 29 | 30 | /* HH_IGNORE_ERROR[4022] exhaustive + default */ 31 | switch ($ts['kind']) { 32 | case TypeStructureKind::OF_VOID: 33 | throw new UnsupportedTypeException('OF_VOID'); 34 | case TypeStructureKind::OF_INT: 35 | /* HH_IGNORE_ERROR[4110] */ 36 | return TypeSpec\int(); 37 | case TypeStructureKind::OF_BOOL: 38 | /* HH_IGNORE_ERROR[4110] */ 39 | return TypeSpec\bool(); 40 | case TypeStructureKind::OF_FLOAT: 41 | /* HH_IGNORE_ERROR[4110] */ 42 | return TypeSpec\float(); 43 | case TypeStructureKind::OF_STRING: 44 | /* HH_IGNORE_ERROR[4110] */ 45 | return TypeSpec\string(); 46 | case TypeStructureKind::OF_RESOURCE: 47 | /* HH_IGNORE_ERROR[4110] */ 48 | return TypeSpec\resource(); 49 | case TypeStructureKind::OF_NUM: 50 | /* HH_IGNORE_ERROR[4110] */ 51 | return TypeSpec\num(); 52 | case TypeStructureKind::OF_ARRAYKEY: 53 | /* HH_IGNORE_ERROR[4110] */ 54 | return TypeSpec\arraykey(); 55 | case TypeStructureKind::OF_NORETURN: 56 | throw new UnsupportedTypeException('OF_NORETURN'); 57 | case TypeStructureKind::OF_DYNAMIC: 58 | case TypeStructureKind::OF_MIXED: 59 | /* HH_IGNORE_ERROR[4110] */ 60 | return new MixedSpec(); 61 | case TypeStructureKind::OF_TUPLE: 62 | /* HH_IGNORE_ERROR[4110] */ 63 | return new TupleSpec( 64 | Vec\map( 65 | TypeAssert\not_null($ts['elem_types']), 66 | from_type_structure<>, 67 | ), 68 | ); 69 | case TypeStructureKind::OF_FUNCTION: 70 | throw new UnsupportedTypeException('OF_FUNCTION'); 71 | case TypeStructureKind::OF_ARRAY: 72 | throw new UnsupportedTypeException('OF_ARRAY'); 73 | case TypeStructureKind::OF_VARRAY: 74 | $generics = $ts['generic_types'] as nonnull; 75 | invariant(C\count($generics) === 1, 'got varray with multiple generics'); 76 | /* HH_IGNORE_ERROR[4110] */ 77 | return TypeSpec\varray(from_type_structure($generics[0])); 78 | case TypeStructureKind::OF_DARRAY: 79 | $generics = $ts['generic_types'] as nonnull; 80 | invariant( 81 | C\count($generics) === 2, 82 | 'darrays must have exactly 2 generics', 83 | ); 84 | /* HH_IGNORE_ERROR[4110] */ 85 | return TypeSpec\darray(from_type_structure($generics[0]), from_type_structure($generics[1])); 86 | case TypeStructureKind::OF_VARRAY_OR_DARRAY: 87 | $generics = $ts['generic_types'] as nonnull; 88 | invariant( 89 | C\count($generics) === 1, 90 | 'got varray_or_darray with multiple generics', 91 | ); 92 | /* HH_IGNORE_ERROR[4110] */ 93 | return TypeSpec\varray_or_darray(from_type_structure($generics[0])); 94 | 95 | case TypeStructureKind::OF_DICT: 96 | $generics = TypeAssert\not_null($ts['generic_types']); 97 | invariant(C\count($generics) === 2, 'dicts must have 2 generics'); 98 | /* HH_IGNORE_ERROR[4110] */ 99 | return TypeSpec\dict( 100 | from_type_structure($generics[0]), 101 | from_type_structure($generics[1]), 102 | ); 103 | case TypeStructureKind::OF_KEYSET: 104 | $generics = TypeAssert\not_null($ts['generic_types']); 105 | invariant(C\count($generics) === 1, 'keysets must have 1 generic'); 106 | /* HH_IGNORE_ERROR[4110] */ 107 | return TypeSpec\keyset(from_type_structure($generics[0])); 108 | case TypeStructureKind::OF_VEC: 109 | $generics = TypeAssert\not_null($ts['generic_types']); 110 | invariant(C\count($generics) === 1, 'vecs must have 1 generic'); 111 | /* HH_IGNORE_ERROR[4110] */ 112 | return TypeSpec\vec(from_type_structure($generics[0])); 113 | case TypeStructureKind::OF_GENERIC: 114 | throw new UnsupportedTypeException('OF_GENERIC'); 115 | case TypeStructureKind::OF_SHAPE: 116 | $fields = TypeAssert\not_null($ts['fields']); 117 | /* HH_IGNORE_ERROR[4110] */ 118 | return new ShapeSpec( 119 | Dict\pull_with_key( 120 | $fields, 121 | ($_k, $field_ts) ==> from_type_structure($field_ts), 122 | ($k, $_v) ==> $k, 123 | ), 124 | ($ts['allows_unknown_fields'] ?? false) 125 | ? UnknownFieldsMode::ALLOW 126 | : UnknownFieldsMode::DENY, 127 | ); 128 | case TypeStructureKind::OF_CLASS: 129 | case TypeStructureKind::OF_INTERFACE: 130 | $classname = TypeAssert\not_null($ts['classname']); 131 | switch ($classname) { 132 | case Vector::class: 133 | case ImmVector::class: 134 | case \ConstVector::class: 135 | return new VectorSpec( 136 | /* HH_IGNORE_ERROR[4323] unsafe generics */ 137 | $classname, 138 | from_type_structure( 139 | TypeAssert\not_null($ts['generic_types'] ?? null)[0], 140 | ), 141 | ); 142 | case Map::class: 143 | case ImmMap::class: 144 | case \ConstMap::class: 145 | return new MapSpec( 146 | /* HH_IGNORE_ERROR[4323] unsafe generics */ 147 | $classname, 148 | from_type_structure( 149 | TypeAssert\not_null($ts['generic_types'] ?? null)[0], 150 | ), 151 | from_type_structure( 152 | TypeAssert\not_null($ts['generic_types'] ?? null)[1], 153 | ), 154 | ); 155 | case Set::class: 156 | case ImmSet::class: 157 | case \ConstSet::class: 158 | return new SetSpec( 159 | /* HH_IGNORE_ERROR[4323] unsafe generics */ 160 | $classname, 161 | from_type_structure( 162 | TypeAssert\not_null($ts['generic_types'] ?? null)[0], 163 | ), 164 | ); 165 | default: 166 | if ( 167 | \is_a( 168 | $classname, 169 | KeyedTraversable::class, 170 | /* strings = */ true, 171 | ) 172 | ) { 173 | return new KeyedTraversableSpec( 174 | /* HH_IGNORE_ERROR[4323] unsafe generics */ 175 | $classname, 176 | from_type_structure( 177 | TypeAssert\not_null($ts['generic_types'] ?? null)[0], 178 | ), 179 | from_type_structure( 180 | TypeAssert\not_null($ts['generic_types'] ?? null)[1], 181 | ), 182 | ); 183 | } 184 | if (\is_a($classname, Traversable::class, /* strings = */ true)) { 185 | return new TraversableSpec( 186 | /* HH_IGNORE_ERROR[4323] unsafe generics */ 187 | $classname, 188 | from_type_structure( 189 | TypeAssert\not_null($ts['generic_types'] ?? null)[0], 190 | ), 191 | ); 192 | } 193 | return new InstanceOfSpec($classname); 194 | } 195 | case TypeStructureKind::OF_TRAIT: 196 | throw new UnsupportedTypeException('OF_TRAIT'); 197 | case TypeStructureKind::OF_ENUM: 198 | $enum = TypeAssert\not_null($ts['classname']); 199 | /* HH_IGNORE_ERROR[4110] */ 200 | return new EnumSpec($enum); 201 | case TypeStructureKind::OF_NULL: 202 | /* HH_IGNORE_ERROR[4110] unsafe generics */ 203 | return new NullSpec(); 204 | case TypeStructureKind::OF_NONNULL: 205 | /* HH_IGNORE_ERROR[4110] unsafe generics */ 206 | return new NonNullSpec(); 207 | case TypeStructureKind::OF_UNRESOLVED: 208 | throw new UnsupportedTypeException('OF_UNRESOLVED'); 209 | default: 210 | $name = TypeStructureKind::getNames()[$ts['kind']] ?? 211 | \var_export($ts['kind'], true); 212 | throw new UnsupportedTypeException($name); 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /src/TypeSpec/__Private/stringish_cast.hack: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | * 8 | */ 9 | namespace Facebook\TypeSpec\__Private; 10 | 11 | function stringish_cast(\Stringish $stringish, string $caller): string { 12 | if ($stringish is string) { 13 | return $stringish; 14 | } else if (\HH\is_fun($stringish)) { 15 | return \HH\fun_get_function($stringish); 16 | } else { 17 | invariant( 18 | $stringish is \StringishObject, 19 | 'Expected Stringish to be either a string or a StringishObject, got %s', 20 | \get_class($stringish) ?: \gettype($stringish), 21 | ); 22 | \trigger_error( 23 | 'Stringish is being deprecated. '. 24 | 'Passing an object that implements __toString to '. 25 | $caller. 26 | '() may not work in a future release.', 27 | \E_USER_DEPRECATED, 28 | ); 29 | /*HH_FIXME[4128] stringish_cast() can't be used in the future.*/ 30 | return $stringish->__toString(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/UnsupportedTypeException.hack: -------------------------------------------------------------------------------- 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 | final class UnsupportedTypeException extends \Exception { 14 | public function __construct(string $type) { 15 | $message = \sprintf("Not able to handle type '%s'", $type); 16 | parent::__construct($message); 17 | } 18 | } 19 | --------------------------------------------------------------------------------