- Rename Typed PHP arrays documentation to distinguish StdList/StdDict metadata - Add Unified Type Annotation Design document covering StdArray/StdVector/StdMap/StdOrderedMap/StdList/StdDict contracts - Add StdFunc / StdArgInfo Type Annotation Design document for proposed signatures, Nullable, references, Optional, Variadic features - Update container parameter attributes to specify type annotation contract rules - Clarify C++ container focus in Std Containers documentation with unified type annotation references - Document StdArray dimension arrays for structural nesting with fixed shape requirements - Specify StdList/StdDict key and value contracts for typed PHP array storage boundariesmaster
parent
f3f686ec46
commit
a7a0e5303f
11 changed files with 657 additions and 4 deletions
@ -0,0 +1,181 @@ |
|||||||
|
# StdFunc / StdArgInfo Type Annotation Design |
||||||
|
|
||||||
|
Status: an agreed design, not implemented. Names, flags, and syntax below describe the target interface, including the new StdArray annotation, not currently available features. See [unified type annotations](TYPE_ANNOTATIONS.md) for terminology and container contracts. |
||||||
|
|
||||||
|
## Goals and non-goals |
||||||
|
|
||||||
|
`StdFunc` specifies a callable's complete invocation contract: argument counts, value types, reference modes, and result type. `StdArgInfo` describes one parameter and its modifiers. |
||||||
|
|
||||||
|
This is not an arrow-function execution optimization. Ordinary statically verifiable PHP callbacks may continue to execute through Zend Bridge. Typed arrays, Native values, or other protected boundaries require separate controlled TypePHP paths. Signature checking does not require converting every closure into a C++ lambda. |
||||||
|
|
||||||
|
## Declaration syntax |
||||||
|
|
||||||
|
```php |
||||||
|
function run( |
||||||
|
#[StdFunc( |
||||||
|
Type::Void, |
||||||
|
[ |
||||||
|
Type::Int, |
||||||
|
new StdArgInfo(Type::Float, default: 1.0), |
||||||
|
new StdArgInfo(Type::Str, Type::Optional), |
||||||
|
new StdArgInfo(Type::Int, Type::Variadic), |
||||||
|
], |
||||||
|
)] |
||||||
|
callable $callback |
||||||
|
): void { |
||||||
|
$callback(10); |
||||||
|
$callback(10, 2.5, 'tag', 1, 2); |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
Proposed interface: `StdArgInfo(type, flags = 0, default = <unset>)`. A bare `Type::Int` is shorthand for `new StdArgInfo(Type::Int)`: required, non-nullable, by value. |
||||||
|
|
||||||
|
All parameter Nullable, Ref, Optional, Variadic, and default information belongs in `StdArgInfo`. Separate `optional:` lists, `variadic:` signature arguments, and nested parameter `RefType` / `NullableType` wrappers are superseded. |
||||||
|
|
||||||
|
PHP attribute arguments do not allow ordinary function calls such as `StdArgInfo(...)` or `std::list(...)`. PHP 8.1 permits `new` expressions in attribute arguments. TypePHP recognizes whitelisted descriptor ASTs without executing their constructors or arbitrary user code. [PHP attribute syntax](https://www.php.net/manual/en/language.attributes.syntax.php), [PHP 8.1 new initializers](https://www.php.net/manual/en/migration81.new-features.php). |
||||||
|
|
||||||
|
Public `Type::Void`, `Type::Nullable`, `Type::Ref`, `Type::Optional`, `Type::Variadic`, and `StdArgInfo` still need implementation. An existing compiler-internal VOID does not implement the public Void descriptor. |
||||||
|
|
||||||
|
## Flags and validation |
||||||
|
|
||||||
|
Flags are integer bitmasks, separate from value type descriptors: |
||||||
|
|
||||||
|
| Flag | Meaning | Omission | |
||||||
|
|---|---|---| |
||||||
|
| None | Required, by value, non-nullable | Forbidden | |
||||||
|
| `Nullable` | Value may be null | Does not change requiredness | |
||||||
|
| `Ref` | Writable reference; both input and output are constrained | Does not change requiredness | |
||||||
|
| `Optional` | Contract supplies an omitted value | Allowed | |
||||||
|
| `Variadic` | Zero or more extra values of the element type | Allowed, meaning zero elements | |
||||||
|
|
||||||
|
Supplying `default` implies Optional. Explicit `default: null` also counts as a supplied default, but requires Nullable or a type that already admits null; it never implicitly adds Nullable. |
||||||
|
|
||||||
|
Required parameters precede Optional parameters. There is at most one Variadic parameter, at the end. Modifiers must make sense for the value type; Void is not a parameter type. Unknown flags, conflicting primary types, and meaningless combinations are compilation errors. |
||||||
|
|
||||||
|
## Optional and defaults |
||||||
|
|
||||||
|
An Optional parameter with no explicit default uses its type's empty value: |
||||||
|
|
||||||
|
| Type | Empty default | |
||||||
|
|---|---| |
||||||
|
| Int / Float | `0` / `0.0` | |
||||||
|
| Bool | `false` | |
||||||
|
| Str / String | `''` | |
||||||
|
| Array | A fresh empty array | |
||||||
|
| StdList / StdDict | A fresh empty PHP array retaining the contract | |
||||||
|
| Nullable type | `null`, taking precedence over the non-null zero value | |
||||||
|
| Non-nullable class object | No safe empty default; compilation error | |
||||||
|
| Other types | Require defined safe initialization or a compilation error | |
||||||
|
|
||||||
|
Do not implicitly instantiate class objects, put null into non-nullable objects, or treat an ordinary empty array as a Box. Automatic creation/destruction of non-nullable Box defaults requires separate evaluation and is not enabled merely by Optional. |
||||||
|
|
||||||
|
Explicit defaults must match the value type. The first phase should accept statically verifiable scalar constants, null, and safe array initializers. Object, Box, and other complex defaults need further design; arbitrary factory functions are not executed to obtain defaults. |
||||||
|
|
||||||
|
Defaults belong to the `StdFunc` contract. Equal parameter types with different defaults do not make two contracts freely interchangeable: omitted calls behave differently. They may share a low-level callable ABI type, but retain their invocation contracts or require an explicit adapter. Propagation must not erase defaults. |
||||||
|
|
||||||
|
An absent default differs from explicit null. AST parsing records argument presence; cache/stub metadata includes a separate `hasDefault`. A future runtime descriptor interface also needs an unset sentinel instead of testing only whether `default` is null. |
||||||
|
|
||||||
|
## Caller-side default completion |
||||||
|
|
||||||
|
This is an agreed semantic choice, not merely a claim that the callback implementation has default parameters: |
||||||
|
|
||||||
|
```php |
||||||
|
// Contract: Optional Int without an explicit default, therefore default 0. |
||||||
|
// Implementation: function handler(int $value = 10): void { ... } |
||||||
|
// Calling through this StdFunc with the argument omitted passes 0, not 10. |
||||||
|
``` |
||||||
|
|
||||||
|
Validate supplied arguments, evaluate them once in PHP order, fill omitted fixed positions using contract defaults, then pass variadic arguments. Default arrays and reference storage are fresh per call; mutable default objects must not be shared accidentally. |
||||||
|
|
||||||
|
The implementation's defaults do not apply to completed positions. `func_num_args()`, `func_get_args()`, and callback backtrace arguments observe the completed fixed arguments. This observable difference is intentional. |
||||||
|
|
||||||
|
Because the caller completes fixed positions, the target's corresponding parameters may be required if they accept the completed values. For example, `(Optional Int = 0) -> Void` can bind `function handler(int $value): void`. The earlier rule that an Optional contract requires an Optional implementation is superseded. |
||||||
|
|
||||||
|
## Nullable values and nullable callback bindings |
||||||
|
|
||||||
|
```php |
||||||
|
new StdArgInfo(Type::Int, Type::Nullable | Type::Ref); |
||||||
|
new StdArgInfo(Type::Int, Type::Nullable | Type::Optional); |
||||||
|
``` |
||||||
|
|
||||||
|
The first is a required nullable-int reference. The second can be omitted and defaults to null. Nullable and Optional are independent. |
||||||
|
|
||||||
|
A nullable callback binding is separate from nullable parameter values. Proposed `StdFunc(..., nullable: true)` can express it, with an omitted PHP type or compatible `?callable`. Non-null contracts permit omitted types or `callable`. Explicit mixed/any is rejected. Calling a nullable callback requires a non-null proof or a runtime non-null check. |
||||||
|
|
||||||
|
The concrete syntax for nullable result types remains undecided. `StdArgInfo` describes parameters only; it must not be reused merely to solve result syntax. Reference returns are deferred. |
||||||
|
|
||||||
|
## Reference parameters |
||||||
|
|
||||||
|
Ref belongs to the signature. Contract and implementation must agree on by-value/by-reference modes. Referenced value types are invariant: `&Int` is not interchangeable with `&Nullable(Int)`, and input contravariance must not widen reference writes. |
||||||
|
|
||||||
|
Calls use ordinary PHP `$callback($value)`, without an argument-side `&`. Initially support statically known writable locals and existing safe reference ABIs. Constants, expressions, and ordinary returned values are invalid reference arguments. Properties, array elements, returned references, and reference escape paths need separate acceptance. |
||||||
|
|
||||||
|
Do not simulate references to existing variables with copy-in/copy-out temporaries. Preserve alias identity, evaluation order, exceptions, and mutations visible during nested calls. |
||||||
|
|
||||||
|
`Optional | Ref` belongs to the target model. Omission creates independent, correctly typed writable storage for the duration of that call, and the callback must not retain its reference. Nullable Optional Ref storage starts with null. Storage lifetime and non-escape proofs are not implemented; the first phase may reject this combination explicitly, never silently lower it to by-value passing. |
||||||
|
|
||||||
|
An unknown dynamic callback cannot gain trusted reference mutation privileges from an annotation alone. |
||||||
|
|
||||||
|
## Variadic parameters |
||||||
|
|
||||||
|
```php |
||||||
|
new StdArgInfo(Type::Str, Type::Variadic); |
||||||
|
new StdArgInfo(Type::Int, Type::Nullable | Type::Variadic); |
||||||
|
``` |
||||||
|
|
||||||
|
The type describes each extra argument, not the collected parameter array. Zero arguments mean no elements, not one default element; Variadic therefore rejects defaults. An explicit Optional flag with Variadic may normalize to Variadic without additional behavior. |
||||||
|
|
||||||
|
Fixed positions, including Optional positions, consume arguments first. Remaining arguments are variadic. Positional calls cannot skip an Optional parameter to supply the variadic tail. An unbounded contract requires compatible variadic capacity in the target. |
||||||
|
|
||||||
|
Initially defer `Variadic | Ref`, named calls, and dynamic unpacking without a static proof of counts and per-item types. PHP user functions tolerating extra arguments is not an exception to contract checking. |
||||||
|
|
||||||
|
## Container parameter types |
||||||
|
|
||||||
|
```php |
||||||
|
#[StdFunc( |
||||||
|
Type::Void, |
||||||
|
[ |
||||||
|
new StdArgInfo(new StdList(MyUser::class), Type::Ref), |
||||||
|
new StdDict(Type::Str, Type::Int), |
||||||
|
new StdVector(Type::Int), |
||||||
|
new StdArray(Type::Int, [100, 200, 8]), |
||||||
|
], |
||||||
|
)] |
||||||
|
``` |
||||||
|
|
||||||
|
Nested `new StdList`, `new StdDict`, `new StdVector`, `new StdMap`, `new StdOrderedMap`, and `new StdArray` are type descriptors sharing canonical contracts with standalone annotations. StdArray uses one length or an outer-to-inner dimension array, not recursive descriptor objects. `new StdArgInfo(new StdArray(Type::Int, 100))` describes a one-dimensional parameter; full shapes must match. |
||||||
|
|
||||||
|
StdArray is a special container annotation form: its second argument describes fixed shape rather than a type. Structural nesting is not parsed using map key/value or vector element-only rules. Embedding it in StdArgInfo does not change this distinction; see the dedicated StdArray section in the unified design. |
||||||
|
|
||||||
|
Container kind, key type, and value type must match. Ordinary array or var/any values do not automatically acquire a trusted contract. Lists and integer-key dicts are distinct. By-value PHP arrays retain COW; by-reference arrays retain aliases. Boxes retain their separate shared lifetime model. |
||||||
|
|
||||||
|
Typed PHP arrays require a statically verifiable TypePHP callback that preserves the same contract. Existing direct calls use `php::Array` / `php::Array &`; an ordinary Zend callable path may lose the contract or lack that ABI. Provide a controlled path or reject the call; `isCallable()` alone is insufficient. |
||||||
|
|
||||||
|
StdList/StdDict references cannot escape into dynamic PHP. StdVector and other Box references are separate capabilities: existing Box annotations reject reference parameters, and nested descriptors do not automatically enable them. Existing Native-object and container escape restrictions remain intact. |
||||||
|
|
||||||
|
## Binding, calls, and propagation |
||||||
|
|
||||||
|
- Accept signature-explicit, target-verifiable closures, arrow functions, first-class TypePHP function/method callables, and existing values with the same contract. |
||||||
|
- Initially reject unknown dynamic strings, method arrays, ordinary var callables, and implementations without a provable signature. Zend Bridge execution does not imply unknown signature provenance. |
||||||
|
- Verify all lowered calls: fixed counts/types, reference modes, variadic capacity, and the result contract. |
||||||
|
- By-value matching follows input contravariance/output covariance. The first phase may restrict this to provable basic-type and Nullable compatibility, deferring complex unions and class variance. References are invariant; typed containers require matching kinds and type arguments. |
||||||
|
- Known type errors, missing required arguments, and excess arguments are compilation errors. Ordinary var/any value arguments can receive strict runtime checks without coercion. This does not recover trusted typed arrays or callback signatures. |
||||||
|
- Verified results propagate to call expressions and receiving locals. Void cannot be used as a value. Unknown dynamic results cannot be trusted and unboxed solely because an annotation says so. |
||||||
|
- Ordinary local assignment propagates the complete contract, including defaults. Rebinding, capture, caching, and forwarding must not lose defaults or boundary rules. An ordinary unannotated boundary does not automatically retain trusted signature metadata. |
||||||
|
- Reference transfer of callback bindings, dynamic replacement, properties, and escapes require separate protection. The first phase covers parameter declarations and controlled local propagation, not implicit property support. |
||||||
|
|
||||||
|
## Implementation phases and acceptance |
||||||
|
|
||||||
|
1. Register annotations and whitelisted descriptor ASTs; add signature/parameter metadata and unset-default handling, serialized into declaration caches and library stubs. |
||||||
|
2. Verify bindings and ordinary required arguments/results; propagate result types at variable calls while retaining ordinary Zend closure execution. |
||||||
|
3. Add Nullable, caller-side Optional completion, Variadic, and evaluation ordering. Contract changes invalidate callers because defaults are lowered at call sites. |
||||||
|
4. Integrate existing safe scalar reference ABIs and prove alias/exception behavior. Independently validate Optional Ref lifetime, escape prevention, and controlled typed-array callbacks; never reuse unsafe copy-in/copy-out bridges. |
||||||
|
5. Keep properties, named calls, unknown dynamic callbacks, reference returns, and dynamic unpacking closed until their boundaries are proven. |
||||||
|
|
||||||
|
Tests cover flag combinations, absent versus explicit-null defaults, nullable defaults, invalid default types, different contract/implementation defaults, required target parameters, observable argument counts, once-only evaluation, variadic positions/types, reference aliases/exceptions/escapes, typed-array COW/references, rejection of dynamic mutation, results, caches, and stub round trips. |
||||||
|
|
||||||
|
## Language references and deliberate differences |
||||||
|
|
||||||
|
TypeScript erases concrete default values from function types while retaining Optional. Python callable specifications allow default placeholders and check statically known default types. This design borrows separation of parameter kinds from value types, but deliberately differs: `StdArgInfo` defaults belong to the invocation contract and are supplied by callers, so contract metadata retains them. |
||||||
|
|
||||||
|
Passing null in PHP does not trigger a function default. Likewise, this design fills omitted arguments only, never treating null as an omission. References: [TypeScript functions](https://www.typescriptlang.org/docs/handbook/functions), [Python callable specification](https://typing.python.org/en/latest/spec/callables.html), [PHP arguments and references](https://www.php.net/manual/en/functions.arguments.php). |
||||||
@ -0,0 +1,138 @@ |
|||||||
|
# Unified Type Annotation Design |
||||||
|
|
||||||
|
Status: a combined record of existing container behavior and target design. The `StdArray` annotation, `StdFunc`, and `StdArgInfo` are not implemented. This document does not announce new available features. |
||||||
|
|
||||||
|
## Terminology and responsibilities |
||||||
|
|
||||||
|
`StdArray`, `StdVector`, `StdMap`, `StdOrderedMap`, `StdList`, `StdDict`, and `StdFunc` are collectively **type annotations**. They describe TypePHP static contracts, rather than runtime validators or compile-time functions that create containers. |
||||||
|
|
||||||
|
- `std::vector(...)`, `std::list(...)`, etc. are compile-time value factories. |
||||||
|
- `#[StdVector(...)]`, `#[StdList(...)]`, etc. annotate parameter or property contracts. |
||||||
|
- Proposed nested `new StdList(...)` expressions describe types; they do not create arrays. |
||||||
|
- `new StdArgInfo(...)` describes one function parameter: its value type, Nullable, reference mode, Optional, Variadic, and default value. |
||||||
|
|
||||||
|
See [StdFunc and StdArgInfo](STD_FUNC_DESIGN.md), [typed PHP arrays](TYPED_ARRAYS.md), and [C++ containers](STD_CONTAINERS.md). |
||||||
|
|
||||||
|
## Type and storage models |
||||||
|
|
||||||
|
| Type annotation | Value factory | Storage and passing | Contract | |
||||||
|
|---|---|---|---| |
||||||
|
| Proposed `StdArray(T, sizeOrDimensions)` | `std::array(T, N)` or existing nested factories | PHPX Box containing fixed-size C++ template instances | Leaf type, full shape, no holes | |
||||||
|
| `StdVector(T)` | `std::vector(T[, size])` | PHPX Box containing a C++ template instance | Contiguous integer indices and element type | |
||||||
|
| `StdMap(K, V)` | `std::map(K, V)` | PHPX Box containing a C++ hash map | Key and value types | |
||||||
|
| `StdOrderedMap(K, V)` | `std::orderedMap(K, V)` | PHPX Box containing a C++ ordered map | Key and value types | |
||||||
|
| `StdList(T)` | `std::list(T)` | Ordinary PHP array with COW | Integer keys, value type, append permitted | |
||||||
|
| `StdDict(K, V)` | `std::dict(K, V)` | Ordinary PHP array with COW | Key and value types, explicit keys required | |
||||||
|
| `StdFunc(R, args)` | Existing function or closure value | Proposed signature-bearing callable | Parameters, result, references, and omission rules | |
||||||
|
|
||||||
|
The `std::array` container exists. Its `StdArray` annotation is a newly included target interface, not yet registered or integrated with parameter recovery, and must not be treated as available behavior. |
||||||
|
|
||||||
|
Similar type arguments do not make storage models interchangeable. A Box is not a PHP array, and annotations do not change PHP's native type system. |
||||||
|
|
||||||
|
## Declarations and compatible PHP types |
||||||
|
|
||||||
|
```php |
||||||
|
class State |
||||||
|
{ |
||||||
|
#[StdVector(Type::Int)] public box $values; |
||||||
|
#[StdList(MyUser::class)] public array $users = []; |
||||||
|
#[StdDict(Type::Str, Type::Int)] public $counts = []; |
||||||
|
} |
||||||
|
|
||||||
|
function append(#[StdList(Type::Int)] array &$items): void |
||||||
|
{ |
||||||
|
$items[] = 42; |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
An annotation supplies the full contract. The PHP type may be omitted or name compatible storage: |
||||||
|
|
||||||
|
- `box` for `StdVector`, `StdMap`, and `StdOrderedMap`. |
||||||
|
- An omitted PHP type or `box` for proposed `StdArray`, not PHP `array` storage. |
||||||
|
- `array` for `StdList` and `StdDict`. |
||||||
|
- A `callable` parameter for proposed `StdFunc`; nullable callback bindings require compatible nullable declarations, as specified in its design. |
||||||
|
|
||||||
|
Explicit `mixed`, `any`, and incompatible PHP types are rejected. `Type::Any` as a container element type is a separate concept and does not permit `mixed $parameter`. |
||||||
|
|
||||||
|
A declaration has one primary type annotation; duplicates and conflicting container kinds are rejected. Existing container parameter/property declarations do not support nullable types or unions. Proposed `StdArgInfo` modifiers do not retroactively enable these declaration features. |
||||||
|
|
||||||
|
## C++ container indices and lifetime |
||||||
|
|
||||||
|
- Fixed-size `std::array` indices must be in `[0, N)`. Known integer-literal indices are checked at compile time; other indices use runtime `safeIndex()` checks. |
||||||
|
- Variable-length `std::vector` does not track its current length at compile time. Reads and writes check `[0, size)`. `$v[size] = ...` is not append; use `$v[] = ...`. |
||||||
|
- Array/vector never contain holes. `unset($v[$i])` resets the element to its default without removing its position. |
||||||
|
- Map/orderedMap keys support Int or Str (String is an alias). Their C++ rules are separate from PHP dict numeric-string key normalization. |
||||||
|
- Existing Box parameter entry checks the Box, container kind, and element contract, then recovers a concrete C++ reference. It does not copy or convert every element; mutations are visible to the caller. |
||||||
|
- Existing Box parameters reject references, variadics, defaults, and property promotion. Native objects cannot cross this Box boundary. |
||||||
|
- Existing static iteration restrictions and runtime alias guards continue to protect structural mutations. |
||||||
|
|
||||||
|
## StdArray: dimension arrays describe nesting |
||||||
|
|
||||||
|
**Special case: StdArray uses a different annotation form from other containers.** StdVector/StdList describe an element type, and StdMap/StdOrderedMap/StdDict describe key/value types. StdArray must describe both its leaf element type and fixed shape; the second argument is a length or dimension array, not a key type or another container type. Only StdArray supports structural nesting, so other containers' type-argument counts and parsing rules cannot be reused unchanged. Declaration checks, matching, caches, and stubs must retain dimensions. |
||||||
|
|
||||||
|
Do not use recursive `new StdArray(new StdArray(...), ...)` descriptors or other container types as structural leaf types. Proposed declarations are: |
||||||
|
|
||||||
|
```php |
||||||
|
function process(#[StdArray(Type::Int, 100)] box $values): void {} |
||||||
|
function matrix(#[StdArray(Type::Int, [100, 200, 8])] box $values): void {} |
||||||
|
``` |
||||||
|
|
||||||
|
The second argument is one length or a nonempty dimension array, ordered outermost to innermost. `[100, 200, 8]` means `100 × 200 × 8`, accessed as `$values[$i][$j][$k]` with respective bounds of 100, 200, and 8. |
||||||
|
|
||||||
|
- `StdArray(T, 100)` and `StdArray(T, [100])` normalize to the same type. |
||||||
|
- Retain the leaf type, resolved class, and outer-to-inner `dimensions`. Equality includes rank, order, and each length, not merely total element count. |
||||||
|
- Dimensions are compile-time integer values, never dynamic variables or function calls. Negative dimensions are invalid; total element/byte calculations check overflow. Whether zero-length dimensions are permitted must explicitly align with fixed-container rules and be tested, rather than accidentally introduce different semantics in the annotation path. |
||||||
|
- Only StdArray supports structural nesting. Multidimensional contracts lower to existing nested C++ `StdArray` storage. Existing nested `std::array(...)` value-factory syntax stays unchanged. |
||||||
|
- Each index consumes one dimension. For example, `$values[$i]` from this three-dimensional container has type `StdArray(T, [200, 8])`. |
||||||
|
- Simpler syntax does not solve subarray ownership. Initially prefer independent copies for ordinary subarray assignment and parameter passing. Shared borrowing, reference assignment, and raw subarray pointers require separate lifetime design and are not automatically enabled. |
||||||
|
- Default initialization recursively preserves the full shape. If Optional StdArray parameters are later enabled, their empty value is a correctly shaped default-initialized container, not ordinary `[]`; allocation, class-element initialization, and lifetime still need validation. |
||||||
|
|
||||||
|
Annotation registration, Box entry checks/recovery, property metadata, subarray propagation, caches, and library stubs must retain the full shape. Existing nested factory support does not implement these parameter/property paths. |
||||||
|
|
||||||
|
## StdList / StdDict keys and values |
||||||
|
|
||||||
|
Typed PHP arrays primarily establish their constraints statically. They introduce no runtime typed-array object and require no PHPX or HashTable changes. |
||||||
|
|
||||||
|
- A list is an integer-key PHP array, allowing negative keys, sparse keys, and holes. It permits append. |
||||||
|
- A dict declares Int or Str keys and requires explicit keys, even for integer-key dicts. |
||||||
|
- Lists and integer-key dicts share storage, but not contracts or parameter compatibility. |
||||||
|
- Non-var/any keys must match their declared type; mismatches are compilation errors, not implicit conversions. |
||||||
|
- Var/any keys use PHPX's internal exact integer/string extraction checks. Generated helpers can use existing `php::toIntExact` / `php::toStringExact` wrappers. Public keyword methods remain `toInt()` / `toString()`; no public `toExactInt()` method is introduced. |
||||||
|
- String-key dicts retain PHP numeric-string key normalization in storage. `foreach` converts normalized numeric keys back to Str, keeping the iteration variable's type definite without forcing HashTable keys to remain strings. |
||||||
|
- Values support PHP value types and non-Native class contracts such as `MyUser::class`. Writes follow static assignment compatibility. `Type::Any` values remain dynamic; arbitrary var values are not automatically trusted as concrete types. |
||||||
|
- Foreach key/value types are definite: Int keys for lists, declared keys for dicts, and the declared value type. |
||||||
|
|
||||||
|
## Propagation, parameters, and aliases |
||||||
|
|
||||||
|
Ordinary local list/dict assignment propagates the contract and preserves PHP COW. Reference assignment propagates the same contract and alias relationship. TypePHP function/method parameters, including reference parameters, require annotations with the same kind and key/value types. |
||||||
|
|
||||||
|
The target design also propagates property contracts: |
||||||
|
|
||||||
|
```php |
||||||
|
$box = $obj->values; // Target: inherit StdVector(Type::Int). |
||||||
|
``` |
||||||
|
|
||||||
|
This is not a completed implementation promise. Properties retain type metadata, but automatic Box-property recovery, ownership lifetime, dynamic replacement checks, and closed PHP-array property mutation protection still require implementation and acceptance tests. A local C++ reference must not borrow a container whose property owner may be replaced or destroyed. |
||||||
|
|
||||||
|
Shared Box contents and PHP-array COW are different semantics. Assignment, parameter passing, and reference passing cannot all use one copy strategy. |
||||||
|
|
||||||
|
## Dynamic PHP boundaries |
||||||
|
|
||||||
|
The goal is preventing dynamic code from invalidating a static contract, not rescanning an entire array on each read. |
||||||
|
|
||||||
|
- Typed PHP arrays permit audited read-only array builtins and TypePHP reads, such as `array_search` and `count`. |
||||||
|
- Dynamic mutation through `array_push`, sorting, references, or callbacks is forbidden. A read-only result does not prove a call has no mutation side effects. |
||||||
|
- `std::ref()`, element references, and dynamic reference escape paths are forbidden. Ordinary callable or unannotated `array` parameters cannot recover a trusted list/dict contract. |
||||||
|
- Array results of read-only operations normally remain ordinary PHP arrays unless their result contract is separately proven and propagated. |
||||||
|
- Annotations do not protect arbitrary Zend objects from dynamic whole-object mutation. Existing PHP-array property checks cover first-level direct element writes, not whole-property replacement, object escapes, or property references. Property reads cannot automatically become trusted typed locals. |
||||||
|
- List/dict descriptors within `StdFunc` grant no exception: both the callback and its bridge must preserve the contract, rather than using an ordinary Zend Bridge that loses metadata. |
||||||
|
|
||||||
|
The historical `ArrayDef` has been removed in favor of `StdList` / `StdDict`. Its old hole restrictions no longer apply to PHP typed arrays. Independent C++ array/vector bounds checks remain intact. |
||||||
|
|
||||||
|
## Metadata and acceptance |
||||||
|
|
||||||
|
Canonical contracts include the kind, key/value types, and resolved class names. Declaration caches must not retain temporary type IDs from a particular build. Declarations, parameters, properties, locals, exported library stubs, and dependency invalidation share the same contract semantics. |
||||||
|
|
||||||
|
Nested descriptors use whitelisted AST parsing, not execution of user functions or constructors. IR and diagnostics distinguish container values, type descriptors, and native PHP types. |
||||||
|
|
||||||
|
Acceptance covers compatible/conflicting PHP types, kind mismatches, classes, strict dynamic keys, string-key iteration, COW and aliases, array/vector bounds, read-only/mutation boundaries, property replacement and lifetime, caching, and stub round trips. Documentation alone must not mark incomplete property protection as implemented. |
||||||
@ -0,0 +1,181 @@ |
|||||||
|
# StdFunc / StdArgInfo 类型注解设计 |
||||||
|
|
||||||
|
状态:已确认的方案,尚未实现。本文中的名称、标志和语法是目标接口,不是当前可用功能,包括新增的 StdArray 类型注解。统一术语及容器类型见 [类型注解统一设计](TYPE_ANNOTATIONS.md)。 |
||||||
|
|
||||||
|
## 目标与非目标 |
||||||
|
|
||||||
|
`StdFunc` 给 callable 声明完整调用契约,约束参数数量、参数值类型、引用方式和返回值。`StdArgInfo` 专门描述一个参数的类型与修饰规则。 |
||||||
|
|
||||||
|
本设计不是箭头函数静态执行优化。普通、可静态验证的 PHP 回调仍可通过 Zend Bridge 执行;只有强类型数组/Native 等边界另有要求时才使用受控 TypePHP 调用路径。不得为了签名约束强制把所有闭包改成 C++ lambda。 |
||||||
|
|
||||||
|
## 声明形式 |
||||||
|
|
||||||
|
```php |
||||||
|
function run( |
||||||
|
#[StdFunc( |
||||||
|
Type::Void, |
||||||
|
[ |
||||||
|
Type::Int, |
||||||
|
new StdArgInfo(Type::Float, default: 1.0), |
||||||
|
new StdArgInfo(Type::Str, Type::Optional), |
||||||
|
new StdArgInfo(Type::Int, Type::Variadic), |
||||||
|
], |
||||||
|
)] |
||||||
|
callable $callback |
||||||
|
): void { |
||||||
|
$callback(10); |
||||||
|
$callback(10, 2.5, 'tag', 1, 2); |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
拟议接口:`StdArgInfo(type, flags = 0, default = <未设置>)`。简单 `Type::Int` 等价于 `new StdArgInfo(Type::Int)`,即必选、非 Nullable、按值参数。 |
||||||
|
|
||||||
|
所有参数的 Nullable、Ref、Optional、Variadic 和默认值集中在 `StdArgInfo`,不再使用独立 `optional:` 列表、`variadic:` 参数或嵌套 `RefType` / `NullableType` 参数描述。 |
||||||
|
|
||||||
|
PHP 属性参数不允许普通函数调用,不能写 `StdArgInfo(...)` 或 `std::list(...)`。PHP 8.1 起允许属性实参中的 `new` 表达式。TypePHP 只识别白名单类型描述 AST,不执行描述对象构造函数或任意用户代码。[PHP 属性语法](https://www.php.net/manual/en/language.attributes.syntax.php)、[PHP 8.1 new 初始化表达式](https://www.php.net/manual/en/migration81.new-features.php) |
||||||
|
|
||||||
|
公共 `Type::Void`、`Type::Nullable`、`Type::Ref`、`Type::Optional`、`Type::Variadic` 和 `StdArgInfo` 均待新增;编译器内部已有 VOID 不代表公共 Void 描述已实现。 |
||||||
|
|
||||||
|
## 标志与组合验证 |
||||||
|
|
||||||
|
标志使用可按位或组合的整数,值类型描述与标志空间分开: |
||||||
|
|
||||||
|
| 标志 | 含义 | 是否允许省略 | |
||||||
|
|---|---|---| |
||||||
|
| 无 | 必选、按值、非 Nullable | 否 | |
||||||
|
| `Nullable` | 值允许为 null | 不改变必选性 | |
||||||
|
| `Ref` | 可写引用,输入和输出均受类型约束 | 不改变必选性 | |
||||||
|
| `Optional` | 可省略,由契约提供缺省值 | 是 | |
||||||
|
| `Variadic` | 零个或多个同类型额外实参 | 是,表示零个元素 | |
||||||
|
|
||||||
|
提供 `default` 自动增加 Optional。显式 `default: null` 也算提供默认值,但只有 Nullable(或类型本身允许 null)时才合法;不能隐式增加 Nullable。 |
||||||
|
|
||||||
|
必选参数必须在所有 Optional 参数之前;Variadic 只能有一个且必须位于最后。Nullable/Ref 等标志只能用于有意义的值类型,Void 不能作为参数类型。未知标志、冲突主类型和无意义组合给出编译错误。 |
||||||
|
|
||||||
|
## Optional 与默认值 |
||||||
|
|
||||||
|
未提供显式 default 的 Optional 参数使用以下类型缺省值: |
||||||
|
|
||||||
|
| 类型 | 缺省值 | |
||||||
|
|---|---| |
||||||
|
| Int / Float | `0` / `0.0` | |
||||||
|
| Bool | `false` | |
||||||
|
| Str / String | `''` | |
||||||
|
| Array | 新的空数组 | |
||||||
|
| StdList / StdDict | 带原契约的新空 PHP 数组 | |
||||||
|
| Nullable 类型 | `null`,优先于非空类型的零值 | |
||||||
|
| 非 Nullable 类对象 | 无安全缺省值,编译错误 | |
||||||
|
| 其他类型 | 必须定义安全初始化规则,否则编译错误 | |
||||||
|
|
||||||
|
不自动创建类对象,不为非 Nullable 对象填 null,也不把普通空数组当作 Box。非 Nullable Box 容器的自动创建与销毁规则尚待专项评估,不因 Optional 自动开放。 |
||||||
|
|
||||||
|
显式默认值必须符合值类型。第一阶段建议只接受可静态验证的标量常量、null 和安全数组初始化;对象、Box 及其他复杂默认值需要另外设计,不能执行任意工厂函数求值。 |
||||||
|
|
||||||
|
默认值是 `StdFunc` 契约的一部分。相同参数类型但默认值不同的两个契约不能无条件互换:缺省调用的可观察行为不同。可以共用底层函数 ABI 类型,但保留各自调用契约或显式适配,不得在类型传导中丢失默认值。 |
||||||
|
|
||||||
|
必须区分“未提供 default”和“显式提供 null”。AST 中记录是否出现该实参;缓存/stub 中使用独立 `hasDefault`。将来描述对象的运行时接口也需要未设置哨兵,不能仅以 `default = null` 判断。 |
||||||
|
|
||||||
|
## 缺省参数由调用端补齐 |
||||||
|
|
||||||
|
这是已确认的语义,与单纯声明“实际回调有默认参数”不同: |
||||||
|
|
||||||
|
```php |
||||||
|
// 契约:Optional Int,没有显式默认值,因此缺省为 0。 |
||||||
|
// 实现:function handler(int $value = 10): void { ... } |
||||||
|
// 通过该 StdFunc 省略参数调用时,handler 接收到的是 0,不是 10。 |
||||||
|
``` |
||||||
|
|
||||||
|
调用步骤:先检查实际实参,按 PHP 求值顺序只求值一次,再为省略的固定参数填充契约缺省值,最后传递变长实参。默认数组及引用存储每次调用独立,不得共享可变默认对象。 |
||||||
|
|
||||||
|
回调自身默认值不会参与这些已补齐位置。`func_num_args()`、`func_get_args()`、回调中的回溯参数会看到补齐后的固定参数;这一可观察差异是有意设计。 |
||||||
|
|
||||||
|
因为调用端补齐固定参数,目标回调的对应参数可以是必选参数,只要能够接受补齐后的值。例如契约 `(Optional Int = 0) -> Void` 可以绑定 `function handler(int $value): void`。不能继续沿用“契约 Optional 则实现必须 Optional”的旧规则。 |
||||||
|
|
||||||
|
## Nullable 与回调本身可空 |
||||||
|
|
||||||
|
```php |
||||||
|
new StdArgInfo(Type::Int, Type::Nullable | Type::Ref); |
||||||
|
new StdArgInfo(Type::Int, Type::Nullable | Type::Optional); |
||||||
|
``` |
||||||
|
|
||||||
|
第一项是必选的 Nullable int 引用;第二项允许省略,缺省 null。Nullable 与 Optional 是独立概念。 |
||||||
|
|
||||||
|
回调本身可空不是参数值 Nullable。拟议 `StdFunc(..., nullable: true)` 可单独表达,PHP 类型可省略或使用兼容 `?callable`;非空契约使用省略类型或 `callable`,不允许显式 mixed/any。调用 Nullable callback 必须证明非 null 或插入运行时非空检查。 |
||||||
|
|
||||||
|
返回值 Nullable 的具体声明语法尚未确定;`StdArgInfo` 只描述参数,不为解决返回语法而复用它。引用返回暂不支持。 |
||||||
|
|
||||||
|
## 引用参数 |
||||||
|
|
||||||
|
Ref 是签名的一部分,契约与实际回调的按值/按引用方式必须一致。引用值类型采用不变规则,不能把 `&Int` 当作 `&Nullable(Int)`,也不能通过参数逆变放宽引用写入类型。 |
||||||
|
|
||||||
|
调用使用 PHP 原生 `$callback($value)`,不在实参处写 `&`。第一阶段只支持静态明确的可写局部变量及已有安全引用 ABI;常量、计算结果和普通返回值不能作为引用实参。属性、数组元素、引用返回值及引用逃逸另行验收。 |
||||||
|
|
||||||
|
不能采用复制入/复制出的临时变量模拟已有变量的引用,必须保留别名、求值顺序、异常和嵌套调用中的可见修改。 |
||||||
|
|
||||||
|
`Optional | Ref` 属于目标模型:参数省略时,每次调用创建独立、正确类型的可写存储,活到调用结束,禁止回调保存该引用。Nullable Optional Ref 的缺省存储保存 null。该组合的存储生命周期和逃逸证明尚未实现;第一阶段可先诊断拒绝,不能静默降低为值传递。 |
||||||
|
|
||||||
|
未知动态回调不能凭注解获得可信的引用写入权限。 |
||||||
|
|
||||||
|
## Variadic 参数 |
||||||
|
|
||||||
|
```php |
||||||
|
new StdArgInfo(Type::Str, Type::Variadic); |
||||||
|
new StdArgInfo(Type::Int, Type::Nullable | Type::Variadic); |
||||||
|
``` |
||||||
|
|
||||||
|
描述每个额外实参,不是整个参数集合的类型。零个实参就是空集合,不提供一个默认元素,因此 Variadic 不允许 default。显式 Optional 与 Variadic 的组合可规范化为 Variadic,无需新增行为。 |
||||||
|
|
||||||
|
固定参数(含 Optional)先占用对应位置,剩余实参进入 Variadic。不能通过位置调用跳过一个 Optional,直接填 Variadic。契约允许无限额外实参时,目标也必须有兼容变长能力。 |
||||||
|
|
||||||
|
第一阶段暂不支持 `Variadic | Ref`、命名调用和无法静态证明数量及逐项类型的动态解包。不能利用 PHP 用户函数可能容忍多余实参的行为绕过契约检查。 |
||||||
|
|
||||||
|
## 容器作为参数类型 |
||||||
|
|
||||||
|
```php |
||||||
|
#[StdFunc( |
||||||
|
Type::Void, |
||||||
|
[ |
||||||
|
new StdArgInfo(new StdList(MyUser::class), Type::Ref), |
||||||
|
new StdDict(Type::Str, Type::Int), |
||||||
|
new StdVector(Type::Int), |
||||||
|
new StdArray(Type::Int, [100, 200, 8]), |
||||||
|
], |
||||||
|
)] |
||||||
|
``` |
||||||
|
|
||||||
|
`new StdList` / `new StdDict` / `new StdVector` / `new StdMap` / `new StdOrderedMap` / `new StdArray` 是嵌套类型描述,并复用独立类型注解的规范化契约。StdArray 使用单长度或外到内的维度数组,不使用递归描述对象;`new StdArgInfo(new StdArray(Type::Int, 100))` 可描述一维参数,完整维度必须匹配。 |
||||||
|
|
||||||
|
StdArray 是容器类型注解中的特殊形式:第二个实参描述固定形状而非类型,其结构性嵌套不能按 map 的 key/value 或 vector 的单元素类型规则解析。嵌入 StdArgInfo 不改变这一特殊性,详见统一设计中的 StdArray 专节。 |
||||||
|
|
||||||
|
种类、键类型和值类型必须匹配;普通 array、var/any 不能自动获得可信契约。list 与整数键 dict 不互换。按值 PHP 数组保留 COW,按引用传递保留别名;Box 仍保留自身共享生命周期模型。 |
||||||
|
|
||||||
|
强类型 PHP 数组只能传递给可静态验证并保留同一约束的 TypePHP 回调。现有直接调用采用 `php::Array` / `php::Array &`,而普通 Zend callable 路径可能丢失契约或不支持该 ABI,必须提供受控调用或拒绝,不能仅检查外层 `isCallable()`。 |
||||||
|
|
||||||
|
StdList/StdDict Ref 不能逃逸到动态 PHP。StdVector 等的 Ref 属于独立能力,现有 Box 类型注解参数不支持引用,不能因可嵌套描述而默认开放。Native 对象及容器原有逃逸规则不变。 |
||||||
|
|
||||||
|
## 回调验证、调用与类型传导 |
||||||
|
|
||||||
|
- 可接受签名明确且目标可验证的闭包、箭头函数、TypePHP 函数和方法的一等 callable,以及已有同契约的值。 |
||||||
|
- 未知动态字符串、动态方法数组、普通 var callable 或无法证明签名的实现,第一阶段拒绝绑定。执行方式是 Zend Bridge,不意味着签名来源必须未知。 |
||||||
|
- 验证目标能接受全部降低后的调用:固定参数数量和类型、引用方式、Variadic 能力、返回值契约均须成立。 |
||||||
|
- 参数按值匹配遵循“输入不能收窄,输出不能放宽”;第一阶段可只支持基础类型和 Nullable 的可证明匹配,复杂联合类型及类方差后续扩展。引用类型必须不变,强类型容器种类和参数必须一致。 |
||||||
|
- 已知错误实参、缺少必选参数和多余实参编译报错。普通值参数的 var/any 实参可插入严格运行时检查,不做隐式转换;不能据此恢复强类型数组或可信回调签名。 |
||||||
|
- 已验证返回类型传导给调用表达式及接收变量;Void 不能用于值表达式。动态未知返回值不能仅凭注解直接解箱为可信类型。 |
||||||
|
- 普通局部赋值传播完整契约,包括 Optional 默认值。签名被重绑定、捕获、缓存或作为参数转传时不能丢失默认值和动态边界规则;普通未注解边界不自动保存可信签名。 |
||||||
|
- 回调绑定的引用传递、动态替换、属性读写和逃逸仍需独立保护;第一阶段以参数声明及受控局部传导为范围,属性功能不自动开放。 |
||||||
|
|
||||||
|
## 实现分层与验收计划 |
||||||
|
|
||||||
|
1. 注册类型注解及白名单描述 AST,建立 `FunctionSignature` / 参数元数据和缺省哨兵,序列化进入声明缓存及 library stub。 |
||||||
|
2. 验证回调绑定,检查普通必选参数和返回值,在变量调用处传播返回类型;保留普通 Zend 闭包执行路径。 |
||||||
|
3. 实现 Nullable、Optional 缺省补齐、Variadic 及求值顺序。签名变化必须使依赖调用方失效,因为默认值位于调用端。 |
||||||
|
4. 接入已有安全标量引用 ABI,证明别名和异常行为;Optional Ref 生命周期、引用逃逸及受控 typed-array 回调分别验收,不复用不安全临时复制。 |
||||||
|
5. 在闭合边界前,不开放属性、命名调用、未知动态回调、引用返回和动态解包。 |
||||||
|
|
||||||
|
测试矩阵包括标志组合、显式 null 与未设置 default、Nullable 缺省、默认值类型错误、契约与实现默认值不同、目标固定参数必选、参数数量可观察行为、逐项求值一次、Variadic 位置与类型、引用别名/异常/逃逸、typed-array COW 与引用、动态修改拒绝、返回类型和缓存/stub 往返。 |
||||||
|
|
||||||
|
## 其他语言的参考与差异 |
||||||
|
|
||||||
|
TypeScript 将具体默认值从函数类型中擦除,只保留 Optional;Python callable 规范也允许默认值占位,并检查可确定默认值的类型。本设计借鉴参数种类和值类型的分离,但有意不同:`StdArgInfo` 默认值属于调用契约并由调用端补齐,必须保留在契约元数据中。 |
||||||
|
|
||||||
|
PHP 传 null 不触发函数默认值;这里同样只在参数省略时补齐,不把 null 当作“未传”。参考:[TypeScript 函数类型](https://www.typescriptlang.org/docs/handbook/functions)、[Python callable 规范](https://typing.python.org/en/latest/spec/callables.html)、[PHP 参数与引用](https://www.php.net/manual/en/functions.arguments.php)。 |
||||||
@ -0,0 +1,138 @@ |
|||||||
|
# 类型注解统一设计 |
||||||
|
|
||||||
|
状态:现有容器实现与目标设计的统一记录;`StdArray` 类型注解及 `StdFunc` / `StdArgInfo` 尚未实现。本文不代表新增功能已经可用。 |
||||||
|
|
||||||
|
## 术语与职责 |
||||||
|
|
||||||
|
`StdArray`、`StdVector`、`StdMap`、`StdOrderedMap`、`StdList`、`StdDict`、`StdFunc` 统一称为“类型注解”。它们描述 TypePHP 静态类型契约,而不是运行时验证器,也不是创建容器的编译期函数。 |
||||||
|
|
||||||
|
- `std::vector(...)`、`std::list(...)` 等是创建值的编译期函数。 |
||||||
|
- `#[StdVector(...)]`、`#[StdList(...)]` 等是声明参数或属性契约的类型注解。 |
||||||
|
- `new StdList(...)` 等在拟议的函数签名中是嵌套类型描述,不创建数组。 |
||||||
|
- `new StdArgInfo(...)` 是函数参数描述,只负责值类型、Nullable、引用、Optional、Variadic 和默认值。 |
||||||
|
|
||||||
|
详细分工见 [StdFunc 与 StdArgInfo](STD_FUNC_DESIGN.md)、[强类型 PHP 数组](TYPED_ARRAYS.md)、[C++ 容器](STD_CONTAINERS.md) 和 [现有容器参数实现](STD_CONTAINER_PARAMETER_ATTRIBUTES.md)。 |
||||||
|
|
||||||
|
## 类型模型与存储 |
||||||
|
|
||||||
|
| 类型注解 | 创建值 | 存储与传递 | 核心契约 | |
||||||
|
|---|---|---|---| |
||||||
|
| `StdArray(T, sizeOrDimensions)`(拟议) | `std::array(T, N)` 或现有嵌套工厂 | PHPX Box,具体 C++ 定长模板实例 | 叶子类型、完整维度、禁止空洞 | |
||||||
|
| `StdVector(T)` | `std::vector(T[, size])` | PHPX Box,具体 C++ 模板实例 | 连续整数索引、元素类型 | |
||||||
|
| `StdMap(K, V)` | `std::map(K, V)` | PHPX Box,C++ 哈希映射 | 键和值类型 | |
||||||
|
| `StdOrderedMap(K, V)` | `std::orderedMap(K, V)` | PHPX Box,C++ 有序映射 | 键和值类型 | |
||||||
|
| `StdList(T)` | `std::list(T)` | 普通 PHP array,保留 COW | 整数键、值类型、允许追加 | |
||||||
|
| `StdDict(K, V)` | `std::dict(K, V)` | 普通 PHP array,保留 COW | 键和值类型、必须显式提供键 | |
||||||
|
| `StdFunc(R, args)` | 已有函数或闭包值 | 拟议的带签名 callable | 参数、返回值、引用与缺省调用规则 | |
||||||
|
|
||||||
|
`std::array` 已实现,`StdArray` 类型注解是新纳入的目标接口,当前尚未注册或接入参数恢复,不能当作已可用功能。 |
||||||
|
|
||||||
|
不同存储模型不能因为类型参数相似就互换。Box 不等于 PHP array;类型注解也不改变 PHP 原生类型系统。 |
||||||
|
|
||||||
|
## 声明与 PHP 类型兼容 |
||||||
|
|
||||||
|
```php |
||||||
|
class State |
||||||
|
{ |
||||||
|
#[StdVector(Type::Int)] public box $values; |
||||||
|
#[StdList(MyUser::class)] public array $users = []; |
||||||
|
#[StdDict(Type::Str, Type::Int)] public $counts = []; |
||||||
|
} |
||||||
|
|
||||||
|
function append(#[StdList(Type::Int)] array &$items): void |
||||||
|
{ |
||||||
|
$items[] = 42; |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
类型注解提供完整契约;PHP 类型可以省略,或者只声明兼容的存储类型: |
||||||
|
|
||||||
|
- `StdVector` / `StdMap` / `StdOrderedMap`:`box`。 |
||||||
|
- 拟议 `StdArray`:省略 PHP 类型或声明为 `box`,不使用 PHP `array` 存储类型。 |
||||||
|
- `StdList` / `StdDict`:`array`。 |
||||||
|
- 拟议 `StdFunc`:`callable` 参数;回调本身可空时需要相容的 Nullable 声明,见专项设计。 |
||||||
|
|
||||||
|
显式 `mixed`、`any` 和其他不兼容类型不能与这些类型注解组合。`Type::Any` 作为容器元素类型是另一个概念,不能据此允许 `mixed $parameter`。 |
||||||
|
|
||||||
|
同一个声明只能有一个主类型注解,不能重复或叠加不同容器种类。现有容器参数/属性类型不支持 Nullable、联合类型;`StdArgInfo` 的修饰能力属于新的函数签名设计,不能倒推为现有声明语法已经支持。 |
||||||
|
|
||||||
|
## C++ 容器的索引与生命周期 |
||||||
|
|
||||||
|
- `std::array` 长度固定,索引必须在 `[0, N)`。已知整数字面量索引进行编译期检查;其他索引仍由运行时 `safeIndex()` 检查。 |
||||||
|
- `std::vector` 长度可变,不做编译期长度追踪。索引读写均检查 `[0, size)`;`$v[size] = ...` 不是追加,`$v[] = ...` 才是追加。 |
||||||
|
- array/vector 严格禁止空洞;`unset($v[$i])` 重置默认元素,不移除位置。 |
||||||
|
- map/orderedMap 的键只支持 Int 或 Str(String 为别名),访问遵循现有 C++ 容器规则,不能套用 PHP dict 的数字字符串键规则。 |
||||||
|
- 现有 Box 参数入口检查 Box、容器种类和元素类型,恢复具体 C++ 引用;不复制整个容器或逐项转换元素。修改内容对调用者可见。 |
||||||
|
- 现有 Box 参数不支持引用、可变参数、默认值和属性提升;Native 对象不能借此跨越 Box 边界。 |
||||||
|
- 容器迭代与结构修改继续受既有静态限制及运行时别名保护约束。 |
||||||
|
|
||||||
|
## StdArray:使用维度数组描述嵌套 |
||||||
|
|
||||||
|
**特殊性备注:StdArray 的类型注解方式与其他容器不同。** StdVector/StdList 只描述元素类型,StdMap/StdOrderedMap/StdDict 描述 key/value 类型;StdArray 必须同时描述叶子元素类型和固定形状,其第二个实参是长度或维度数组,不是 key 类型或另一个容器类型。只有 StdArray 支持结构性嵌套,因此不能直接复用其他容器的类型实参数量及解析规则;声明检查、类型匹配、缓存和 stub 均需保留维度信息。 |
||||||
|
|
||||||
|
不使用递归 `new StdArray(new StdArray(...), ...)` 描述,也不允许以其他容器类型作为叶子类型。拟议声明为: |
||||||
|
|
||||||
|
```php |
||||||
|
function process(#[StdArray(Type::Int, 100)] box $values): void {} |
||||||
|
function matrix(#[StdArray(Type::Int, [100, 200, 8])] box $values): void {} |
||||||
|
``` |
||||||
|
|
||||||
|
第二个实参为一个长度,或者非空维度数组。维度按外层到内层排列,`[100, 200, 8]` 表示 `100 × 200 × 8`,其访问形式是 `$values[$i][$j][$k]`,各层分别检查 100、200、8 的边界。 |
||||||
|
|
||||||
|
- `StdArray(T, 100)` 与 `StdArray(T, [100])` 规范化为相同类型。 |
||||||
|
- 保存叶子类型、解析后的类名及统一外到内的 `dimensions`;类型比较包含维度数量、顺序和每层长度,不比较元素总数来判断相等。 |
||||||
|
- 维度必须是编译期可确定的整数,不能使用动态变量或函数调用,不能为负数,计算总元素数和字节数须检查溢出。零长度维度是否允许需与现有定长容器规则明确对齐后验收,不能在新注解路径上意外获得不同语义。 |
||||||
|
- 只有 StdArray 支持结构性嵌套;多维类型降低为现有嵌套 C++ `StdArray`,不修改底层存储。现有创建值的嵌套 `std::array(...)` 工厂语法保持不变。 |
||||||
|
- 每消费一层索引,剩余维度组成子数组类型,例如三维容器的 `$values[$i]` 为 `StdArray(T, [200, 8])`。 |
||||||
|
- 简化声明语法不消除子数组所有权问题。建议首阶段普通子数组赋值及作为参数传递采用独立复制;共享借用、引用赋值和子数组裸指针传递仍需独立生命周期设计,不因维度数组语法自动开放。 |
||||||
|
- 嵌套元素默认初始化必须递归保留完整形状。如果后续允许 Optional 的 StdArray,缺省值是该形状的默认初始化容器,不是普通 `[]`;创建、类元素初始化与生命周期仍需验证。 |
||||||
|
|
||||||
|
StdArray 类型注解接入时还需在注册、Box 入口检查/恢复、属性元数据、子数组类型传导、缓存和 library stub 中保存完整维度。现有工厂支持嵌套,不代表这些参数/属性路径已实现。 |
||||||
|
|
||||||
|
## StdList / StdDict 的键和值 |
||||||
|
|
||||||
|
强类型 PHP 数组的约束主要在静态阶段建立,不增加运行时强类型数组对象,也不修改 PHPX 或 PHP HashTable。 |
||||||
|
|
||||||
|
- list 是整数键 PHP 数组,允许负数、稀疏键和空洞,不是连续序列;支持 `[]` 追加。 |
||||||
|
- dict 的键只能声明为 Int 或 Str,必须显式提供键,包括整数键 dict 也不允许追加。 |
||||||
|
- list 与整数键 dict 的存储相同,但类型契约不同,不能作为参数互换。 |
||||||
|
- 非 `var/any` 键必须与声明类型匹配,否则编译错误,不隐式转换。 |
||||||
|
- `var/any` 键生成 PHPX 内部 `toExactInt` / `toExactString` 严格取值检查;生成辅助调用可通过现有 `php::toIntExact` / `php::toStringExact` 封装,不向用户暴露新关键词方法。用户显式转换仍使用 `toInt()` / `toString()`。 |
||||||
|
- 字符串 dict 保留 PHP 数字字符串键的底层规范化行为;`foreach` 取 key 时强转回 Str,保证遍历变量类型明确,不要求底层 HashTable 永远使用字符串键。 |
||||||
|
- 值支持 PHP 值类型和非 Native 的 `MyUser::class`。值写入遵循静态赋值兼容规则;`Type::Any` 值保留动态类型,不自动信任任意 `var` 为具体类型。 |
||||||
|
- `foreach` 的 key/value 类型明确:list key 为 Int,dict key 为其声明类型,value 为声明值类型。 |
||||||
|
|
||||||
|
## 类型传导、参数与别名 |
||||||
|
|
||||||
|
局部 list/dict 的普通赋值传导契约并保持 PHP COW;引用赋值传导相同契约及别名关系。向 TypePHP 函数或方法传递时,目标必须有同种、同 key/value 类型的类型注解;按引用传递也必须匹配。 |
||||||
|
|
||||||
|
目标设计要求属性读取同样传导契约,例如: |
||||||
|
|
||||||
|
```php |
||||||
|
$box = $obj->values; // 目标:继承 StdVector(Type::Int) |
||||||
|
``` |
||||||
|
|
||||||
|
当前不能把这段目标行为当作完整实现:属性已保存类型元数据,但 Box 属性到局部容器的自动恢复、持有者生命周期、动态替换检查,以及 PHP-array 属性的封闭写入保护仍需实现和验收。不得直接借用一个可能被替换或销毁的属性所持 C++ 容器引用。 |
||||||
|
|
||||||
|
Box 内容共享与普通 PHP 数组 COW 不同;赋值、参数传递、引用不能统一降低为一种复制策略。 |
||||||
|
|
||||||
|
## 动态 PHP 安全边界 |
||||||
|
|
||||||
|
目标是不让动态代码破坏静态契约,而不是在每次读值时重新扫描整个数组。 |
||||||
|
|
||||||
|
- 强类型 PHP 数组允许经审查的只读 array 内置函数和 TypePHP 读取操作,例如 `array_search`、`count`。 |
||||||
|
- 禁止 `array_push`、排序等动态修改,以及可能借引用或 callback 修改数组的路径;“返回值只读”不等于“调用无副作用”。 |
||||||
|
- 禁止 `std::ref()`、元素引用、动态引用传递等逃逸。不能通过一个普通 callable 或函数参数类型 `array` 恢复可信的 list/dict 契约。 |
||||||
|
- 只读操作的数组结果通常是普通 PHP 数组,除非另外证明并传播结果契约。 |
||||||
|
- 类型注解无法保护任意 Zend 对象被动态代码整体修改。现有 PHP-array 属性只检查第一层直接元素赋值,不完整覆盖整属性替换、对象逃逸或属性引用;属性读取不能自动被当作可信强类型局部数组。 |
||||||
|
- `StdFunc` 中出现 list/dict 不豁免这些规则;回调目标及桥接路径必须保留契约,不能直接走会丢失元数据的普通 Zend Bridge。 |
||||||
|
|
||||||
|
历史 `ArrayDef` 已删除,统一使用 `StdList` / `StdDict`。其旧空洞限制不再适用于 PHP 数组模型,也没有删除独立的 C++ array/vector 边界检查。 |
||||||
|
|
||||||
|
## 编译器元数据与验收 |
||||||
|
|
||||||
|
契约使用规范化结构,至少包含种类、key/value 类型和解析后的类名;声明缓存不保存某次构建临时分配的类型 ID。声明、参数、属性、局部变量、导出 library stub 和依赖失效应共享同一契约语义。 |
||||||
|
|
||||||
|
新增嵌套描述对象通过白名单 AST 解析,不执行用户函数或构造函数。容器值、类型描述和 PHP 原生类型必须在 IR 与诊断中区分。 |
||||||
|
|
||||||
|
验收至少覆盖兼容/冲突 PHP 类型、种类不匹配、类类型、动态键严格检查、字符串 dict 遍历、COW 与引用别名、array/vector 边界、只读与动态修改边界、属性替换和生命周期、缓存及 stub 往返。尚未完成的属性保护不能因新增文档而标记为已实现。 |
||||||
Loading…
Reference in new issue