From 422bdc4ce6580984f79dc63cc4ed536e5545cae7 Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Tue, 25 Aug 2026 13:35:12 +0800 Subject: [PATCH] docs: remove documentation and plans for code reuse and encapsulation - Removed CLAUDE.md guidance file for Claude Code usage - Removed code reuse improvement plan documentation - Removed encapsulation review documentation - Updated composer.json to specify phpstan version constraint - Fixed namespace issues in Extractor.php exception handling - Renamed main function in bin/extractor.php to avoid conflicts - Updated LICENSE to standard GPL format --- .claude/plans/code-reuse-improvement.md | 274 -------------------- .claude/plans/encapsulation-review.md | 325 ------------------------ .github/workflows/tests.yml | 69 ++++- CLAUDE.md | 103 -------- LICENSE | 5 +- bin/extractor.php | 6 +- composer.json | 2 +- composer.lock | 2 +- phpunit/src/Platform/PlatformTest.php | 3 + run-tests.php | 2 +- src/Extractor.php | 6 +- src/Optimizer/LoopVarOptimizer.php | 14 +- src/Parser/MethodCallTrait.php | 8 +- src/Platform/PlatformBase.php | 15 ++ src/Platform/UnixPlatform.php | 15 ++ src/Platform/Windows.php | 5 + tests/compiler/array_method/method.phpt | 6 +- 17 files changed, 128 insertions(+), 732 deletions(-) delete mode 100644 .claude/plans/code-reuse-improvement.md delete mode 100644 .claude/plans/encapsulation-review.md delete mode 100644 CLAUDE.md diff --git a/.claude/plans/code-reuse-improvement.md b/.claude/plans/code-reuse-improvement.md deleted file mode 100644 index 2d74416e..00000000 --- a/.claude/plans/code-reuse-improvement.md +++ /dev/null @@ -1,274 +0,0 @@ -# Code Reuse Improvement Plan - -## Analysis Summary - -| Metric | Value | -|--------|-------| -| Total source lines | ~11,000 (PHP only) | -| CompilerBase | 5,917 lines, 269 methods, 20 traits | -| Gcc↔Clang duplication | ~70-80% of methods | -| Linux↔Macos duplication | ~80% of methods | -| `fatalError()` call sites | 174+ across codebase | -| Test setUp/tearDown dup | 4+ test classes | - ---- - -## Phase 1: High-Impact Backend/Platform Deduplication (P0) - -### 1.1 Extract `UnixPlatform` base class - -**Files**: `Platform/Linux.php` (249 lines), `Platform/Macos.php` (278 lines) - -These 13 methods are 100% identical between Linux and Macos: -- `getIncludeFlags()`, `getLibraryPathFlags()`, `getObjectExtension()`, `getExecutableExtension()`, `getPathSeparator()`, `getPhpDir()`, `getRpathOptions()`, `getPicFlag()`, `buildPhpIncludePaths()`, `findPhpConfig()`, `buildPhpLibPaths()` - -Near-identical with minor parameterization: -- `getLibraryFlags()` — only the regex differs (`.a|.so` vs `.a|.dylib`) -- `detectPhpLibs()` — only the lib name differs (`libphp.so` vs `libphp.dylib`) - -**Plan**: Create `UnixPlatform extends PlatformBase` between `PlatformBase` and `Linux`/`Macos`. Move all identical methods up. Add abstract `getSharedLibraryExtension()` (already exists) and a protected `getSharedLibName()` for the single differing method. - -**Expected savings**: ~180 lines removed, ~150 lines added = net ~30 lines but massive maintainability gain. - -### 1.2 Extract `GccLikeBackend` base class - -**Files**: `Backend/Gcc.php` (379 lines), `Backend/Clang.php` (515 lines) - -These methods are structurally identical with only Windows-specific branching: -- `compileFile()`, `linkObjects()`, `buildCompileCommand()`, `buildCCompileCommand()`, `buildNativeCompileCommand()`, `buildLinkCommand()`, `buildCompileOptions()`, `buildLinkOptions()`, `buildFullCompileOptions()`, `buildFullLinkOptions()` - -**Plan**: Create `GccLikeBackend extends CompilerBackend` with all shared logic. Define template-method hooks for the differences: -- `getCompilerSpecificFlags()` — empty for Gcc, MSVC compat flags for Clang/Windows -- `getOutputFlag($isWindows)` — `-o` vs `/OUT:` -- `getSanitizerFlag($type)` — handle the `address`/`addr` aliasing difference -- `getPICHandling($config)` — Gcc always adds `-fPIC`, Clang skips on Windows - -**Expected savings**: ~250+ lines removed from Gcc.php and Clang.php. Msvc.php is sufficiently different (different flag syntax) to remain standalone. - ---- - -## Phase 2: CompilerBase Internal Deduplication (P1) - -### 2.1 Consolidate Big* type dispatch in BinaryOpTrait - -**File**: `Parser/BinaryOpTrait.php` (lines 31-107) - -The three blocks for BigFloat (lines 31-54), Decimal (lines 56-76), and BigInt (lines 78-107) in `parseBinaryOp()` share identical structure: -1. Check if either operand is the big type -2. Guard against incompatible mixing -3. Convert the non-matching operand -4. Dispatch to arithmetic or comparison operator - -**Plan**: Extract `parseBigNumBinaryOp(string $type, string $left, string $right, ...)` parameterized by type name, conversion function, and operator maps. Same refactoring applies to `genBigNumericCmp()` (lines 315-355). - -**Expected savings**: ~40 lines. - -### 2.2 Data-driven operator dispatch tables - -**Files**: `Parser/BinaryOpTrait.php` (16 wrapper methods), `Parser/AssignOpTrait.php` (14 wrapper methods) - -30+ thin methods that are just `parseBinaryOp($left, $right, '+')` / `parseAssignOp($node, '+=')`. - -**Plan**: Replace with a static map in `parseExpr()`: -```php -private const BINARY_OP_MAP = [ - 'Expr_BinaryOp_Plus' => '+', - 'Expr_BinaryOp_Minus' => '-', - // ... -]; -private const ASSIGN_OP_MAP = [ - 'Expr_AssignOp_Plus' => '+=', - // ... -]; -``` - -**Expected savings**: ~200 lines removed (boilerplate method bodies). - -### 2.3 Deduplicate call dispatch patterns - -**Files**: `CompilerBase.php` (`parseFuncCall`, `parseMethodCall`, `parseStaticCall` — ~300 lines combined), `UniversalMethodCall.php` (`tryOptimizePhpFn` vs `dispatchFuncCall`) - -These share the same overall flow: resolve callable → try native/optimized path → on `PlaceHolder` fall back to placeholder → parse args → wrap in `php::call()`. Additionally, `tryOptimizePhpFn()` (UniversalMethodCall lines 720-772) duplicates the argument type conversion logic already present in `dispatchFuncCall()` (FuncCallOptimizer lines 234-269). - -**Plan**: Extract a shared `resolveCall(CallLike $expr, ...)` method. Unify arg conversion so `tryOptimizePhpFn` delegates to `dispatchFuncCall` instead of reimplementing it. - -**Expected savings**: ~40 lines, fixes double-calculation of arg conversions. - -### 2.4 Deduplicate UNIVERSAL_METHODS math entries - -**File**: `UniversalMethodCall.php` (lines 12-69) - -The INT block (lines 12-41) and FLOAT block (lines 42-69) contain 20 identical math method entries (`abs`, `ceil`, `floor`, `sqrt`, `sin`, `cos`, etc.) differing only in `return_type`. Also the `calc_op` entries (add/sub/mul/div) are duplicated. - -**Plan**: Define math method names once in a shared array, generate both INT and FLOAT entries in the constructor with the appropriate `return_type`. - -**Expected savings**: ~25 lines of config data. - -### 2.5 Deduplicate constant folding methods - -**File**: `Optimizer/FuncCallOptimizer.php` (lines 515-593) - -8 methods (`doFoldStringLen`, `doFoldStringCase`, `doFoldCmp2`, `doFoldCmp3`, `doFoldCountLiteral`, `doFoldKnownClass`, `doFoldKnownConstant`, `doFoldSsaType`) follow the identical pattern: extract args → check types → compute → return literal or false. - -**Plan**: Create a generic `tryFold(callable $check, callable $compute)` that handles the arg extraction and short-circuit boilerplate. Each folder becomes a one-liner. - -**Expected savings**: ~50 lines. - -### 2.6 Remove MSVC compat flag duplication in Clang - -**File**: `Backend/Clang.php` - -The 4-line MSVC compatibility block (`-fms-compatibility`, `-fms-compatibility-version=19.40`, `-fdelayed-template-parsing`, `-fms-extensions`) appears 7 times (compileFile, buildCompileCommand, buildCCompileCommand, buildNativeCompileCommand, buildFullCompileOptions, buildCompileOptions, buildLinkOptions). - -**Plan**: Extract `private function getMsvcCompatFlags(): string` method. Called once per method that needs it instead of repeated inline. - -**Expected savings**: ~24 lines, single point of change if MSVC compat flags need updating. - -### 2.7 Merge return-check blocks - -**File**: `CompilerBase.php`, `parseReturn()` (line 1621) and `genReturnCode()` (line 5741) - -Identical 7-line union type check blocks. - -**Plan**: Extract `genUnionReturnWrapper(string $exprVar)` method. - -**Expected savings**: ~10 lines, eliminates drift risk. - -### 2.8 Fix `buildCCompileCommand()` inconsistency between Gcc and Clang - -**Files**: `Backend/Gcc.php` (lines 131-137), `Backend/Clang.php` (lines 198-206) - -Gcc unconditionally appends `-O$level` then conditionally appends `-g`. Clang treats debug and optimization as mutually exclusive (`if debug: -O0 -g` else `-O$level`). This is a behavioral inconsistency between backends implementing the same abstract method. - -**Plan**: Standardize on one behavior (the Clang pattern of `-O0 -g` for debug is the correct one — debug builds should not optimize). This will be automatically resolved by Phase 1.2 (GccLikeBackend). - ---- - -## Phase 3: Structural Improvements (P2) - -### 3.1 Entity flag-check consistency - -**File**: `Entity/PropertyDef.php` has `isPrivate()`, `isProtected()`, `isPublic()`, `isStatic()`. `Entity/MethodDef.php` has none — flag checks are done inline in CompilerBase. - -**Plan**: Add a `HasFlags` trait used by both `PropertyDef` and `MethodDef`: -```php -trait HasFlags { - public function isPrivate(): bool { return $this->flags & Modifiers::PRIVATE; } - public function isProtected(): bool { return $this->flags & Modifiers::PROTECTED; } - public function isPublic(): bool { return !$this->isPrivate() && !$this->isProtected(); } - public function isStatic(): bool { return $this->flags & Modifiers::STATIC; } - public function isAbstract(): bool { return $this->flags & Modifiers::ABSTRACT; } -} -``` - -**Expected savings**: Removes inline flag checks from CompilerBase, adds clarity. - -### 3.2 Test infrastructure base class - -**Files**: `phpunit/src/AstNodeTypeTest.php`, `CompilerBaseAdapterTest.php`, `TraitsTest.php`, `PreprocessorTest.php` - -All 4 duplicate the same setUp/tearDown pattern: create temp dir, `CompilerTest::create()`, recursive cleanup. - -**Plan**: Add `CompilerTestCase extends \PHPUnit\Framework\TestCase` to `phpunit/bootstrap.php`: -```php -abstract class CompilerTestCase extends TestCase { - protected string $tmpDir; - protected CompilerTest $compiler; - - protected function setUp(): void { - parent::setUp(); - $this->tmpDir = sys_get_temp_dir() . '/compiler_test_' . uniqid(); - mkdir($this->tmpDir, 0777, true); - $this->compiler = CompilerTest::create($this->tmpDir); - } - - protected function tearDown(): void { - parent::tearDown(); - // recursive cleanup - } -} -``` - -### 3.3 Eliminate `buildFull*Options` / `build*Options` duality - -**Files**: `Backend/Gcc.php`, `Backend/Clang.php`, `Backend/Msvc.php` - -All three backends implement both `buildFullCompileOptions()` / `buildCompileOptions()` and `buildFullLinkOptions()` / `buildLinkOptions()`. The "full" variants are subsets of the "standard" variants working from differently-keyed option arrays. They have drifted independently (e.g., RPATH handling differs between the two in Gcc/Clang). - -**Plan**: Make the "full" variants delegate to the "standard" variants by normalizing their option keys once at the call site. Keep only one code path for each (compile/link). - -**Expected savings**: ~100+ lines, eliminates drift between the two variants. - -### 3.4 Deduplicate Preprocessor AST switch - -**File**: `Preprocessor.php` - -`prepareFile()` (lines 115-151) and `prepareNamespace()` (lines 196-223) both switch over the same set of AST `Stmt_*` types with nearly identical case bodies. - -**Plan**: Extract `processStmt(Node $v)` method that both callers share. - -**Expected savings**: ~25 lines. - -### 3.5 Remove dead code: `ScopeContext` - -**File**: `Context/ScopeContext.php` (7 lines) - -An empty class with no properties or methods. Used only as a type annotation in `FunctionContext`. Either populate it with scope-relevant state, or remove it and use plain `\stdClass` / array / null. - -### 3.6 Reduce StdContainerTrait coupling - -**File**: `Parser/StdContainerTrait.php` (823 lines, 48 methods) - -This is effectively a standalone subsystem for std container handling. As a trait, it has unrestricted access to CompilerBase's internals. - -**Plan**: Extract core logic into `StdContainerHandler` service class. The trait becomes a thin facade that delegates to the handler. - -**Expected savings**: Better testability, clearer boundaries, easier to understand. - ---- - -## Phase 4: Longer-Term Architectural (P3) - -### 4.1 Break CompilerBase into domain-specific classes - -Currently CompilerBase is a 5,917-line god class using 20 traits as a workaround for PHP's single inheritance. Consider: - -- `ExpressionCompiler` — all parseExpr sub-dispatch (~500 lines) -- `StatementCompiler` — parseStmts, parseIf, parseWhile, parseFor, parseSwitch, etc. -- `TypeResolver` — parseTypeDecl, detectClassOfExpr, type checking -- `CallResolver` — parseFuncCall, parseMethodCall, parseStaticCall, parseNew - -These would be injected services rather than traits, making CompilerBase a coordinator. - -### 4.2 Shared AST walker pattern with Python Translator - -Both PHP and Python translators implement the same "walk-collect-indent-emit" pipeline independently. `Core\Translator` could define a standard `walkAst($nodes, callable $visitor)` that handles indentation and line collection. - ---- - -## Implementation Order & Impact Matrix - -| # | Item | Savings | Risk | Effort | -|---|------|---------|------|--------| -| 1.1 | UnixPlatform base class | ~180 dup lines | Low | 2-3h | -| 1.2 | GccLikeBackend base class | ~250 dup lines | Medium | 3-4h | -| 2.1 | BigNum dispatch consolidation | ~40 lines | Low | 1h | -| 2.2 | Data-driven op dispatch | ~200 lines | Low | 1-2h | -| 2.3 | Unify call dispatch patterns | ~40 lines | Low | 1-2h | -| 2.4 | UNIVERSAL_METHODS math dedup | ~25 lines | Low | 30m | -| 2.5 | Fold method template | ~50 lines | Low | 1h | -| 2.6 | MSVC compat flags in Clang | ~24 lines | Low | 30m | -| 2.7 | Merge return-check blocks | ~10 lines | Low | 30m | -| 2.8 | Fix buildCCompileCommand drift | bug fix | Low | 30m | -| 3.1 | HasFlags trait | clarity | Low | 1h | -| 3.2 | CompilerTestCase base class | boilerplate | Low | 1h | -| 3.3 | Eliminate Full*Options duality | ~100 lines | Medium | 2h | -| 3.4 | Preprocessor AST switch dedup | ~25 lines | Low | 1h | -| 3.5 | Remove dead ScopeContext | 7 lines | Low | 15m | -| 3.6 | StdContainer service class | boundary | Medium | 3-4h | -| 4.1 | Domain classes | architecture | High | 1-2 weeks | -| 4.2 | AST walker pattern | architecture | Medium | 3-5h | - -**Total estimated savings**: ~950+ lines of duplicated / dead code. - -**Recommended execution**: Phase 1 → Phase 2 → Phase 3. Items within each phase are independent and can be parallelized. diff --git a/.claude/plans/encapsulation-review.md b/.claude/plans/encapsulation-review.md deleted file mode 100644 index 4f379855..00000000 --- a/.claude/plans/encapsulation-review.md +++ /dev/null @@ -1,325 +0,0 @@ -# Encapsulation Review - -## Summary - -| Metric | Value | -|--------|-------| -| Entity classes with all-public fields | 9/9 (100%) | -| FunctionContext public properties | 26 (all mutable) | -| CompilerBase private methods | 5/269 (1.9%) | -| Traits with direct `$this->context->` access | 6 traits, 60+ sites | -| ScopeContext (dead code) | 7 lines, empty class | - ---- - -## Issue 1: Entity classes — all-public mutable fields - -**Severity**: High. Every entity class exposes all internal state as public writable properties. External code in Preprocessor/CompilerBase directly mutates them. - -### 1.1 ClassDef (18 public properties) - -`src/Php/Entity/ClassDef.php` - -```php -public array $methods = []; // externally populated: $classDef->properties[$name] = ... -public array $properties = []; // externally populated -public array $constants = []; // externally populated -public array $implements = []; // externally populated -public string $extends = ''; // externally set: $this->classDef->extends = ... -public bool $requireCtor = false; -public bool $enum = false; -public ?string $enumBackingType = null; -public array $enumCases = []; -public array $abstractMethods = []; -public ?Trait_ $trait = null; -public array $traitAliases = []; -public array $traitIgnored = []; -public int $flags; // no visibility checks, raw bitmask -public bool $inheritedFromInternalClass = false; -public string $ctorInit = ''; // mutated during code generation -public string $ctorClean = ''; // mutated during code generation -public FunctionContext $propertyContext; // set after construction -``` - -**Issues**: -- `$properties`, `$methods`, `$constants` — exposed as raw arrays. External code does `$classDef->properties[$name] = $propDef`. No validation that the key matches `$propDef->name`, no type enforcement. -- `$flags` — raw int, no guarantee it's a valid Modifiers bitmask. -- `$ctorInit` / `$ctorClean` — mutated by CompilerBase during code generation, not initialization. -- Property additions use `addMethod()`, `addAbstractMethod()` but array properties are also set directly via `[] =`. -- `$extends` — set directly as raw string, bypasses `parent::__construct()` which also sets it on ClassLikeDef. - -**Recommendation**: -- Make `$methods`, `$properties`, `$constants` private, expose via `addMethod()`/`getMethod()` (already exists) -- Make `$flags` private, expose `isAbstract()` (already exists), add `isFinal()`, `isReadonly()` -- Make `$extends` write-once via `setExtends(string)` with validation -- Add `appendCtorInit(string)` and `appendCtorClean(string)` methods instead of direct string mutation - -### 1.2 FunctionDef (12 public properties) - -`src/Php/Entity/FunctionDef.php` - -```php -public string $name; -public string $returnType; -public array $argInfoList = []; // externally populated: $functionDef->argInfoList[] = $argInfo -public int $argCountRequired = 0; -public string $params = ''; // generated C++ param string, mutated during compilation -public string $namespace; -public bool $method = false; -public bool $stub = false; -public bool $returnTypeUndeclared = false; -public string $returnClass = ''; -public ?array $returnTypeCheck = null; -public string $returnTypeStr = ''; -public ?NodeAbstract $returnTypeNode = null; -``` - -**Issues**: -- `$name` and `$namespace` are set in constructor but still publicly writable — should be readonly -- `$argInfoList[]` is directly appended to by Preprocessor (line 329) -- `$params` is a codegen artifact stored on the entity — belongs in a separate compilation context - -**Recommendation**: -- Make constructor-set properties readonly (`$name`, `$namespace`, `$returnType`) -- Add `addArg(ArgInfo $arg)` method instead of direct array mutation -- Extract `$params` to a compilation context separate from the definition entity - -### 1.3 PropertyDef (7 public properties) - -`src/Php/Entity/PropertyDef.php` - -```php -public string $name; -public string $type; -public int $flags; -public ?string $default = null; -public ?ArrayInitPlan $arrayInitPlan = null; -public bool $nullable = false; -public string $class = ''; // set after construction -``` - -**Issues**: -- `$class` is set after construction externally (`$propDef->class = $fullClassName`) -- `$flags` is raw int — already has `isPrivate()`/`isProtected()`/`isPublic()`/`isStatic()` methods, good -- Constructor already sets all core fields — `$class` should be added to the constructor - -**Recommendation**: -- Add `$class` to the constructor (it's always known at construction time) -- Make constructor-set fields readonly or private - -### 1.4 MethodDef (4 public properties) - -`src/Php/Entity/MethodDef.php` - -```php -public int $flags; -public string $name; -public ?FunctionDef $functionDef = null; // set after construction -public bool $hasDynamicCall = false; -``` - -**Issues**: -- No flag-check methods — inline checks in CompilerBase should use `$methodDef->isPrivate()` instead -- `$functionDef` is set externally: `$this->methodDef->functionDef = $functionDef` (Preprocessor line 411) - -**Recommendation**: -- Add `HasFlags` trait (from Phase 3.1 of reuse plan) -- Add `setFunctionDef(FunctionDef $fd)` method with validation - -### 1.5 ConstantDef (8 public properties) - -```php -public string $name; -public string $type; -public int $flags; -public string $value; -public string $arrayExpr = ''; -public string $class = ''; -public ?NodeAbstract $valueExpr = null; -``` - -**Issues**: Same pattern — constructor sets core fields, but `$class` is set externally afterward. - -**Recommendation**: Add `$class` to constructor. - ---- - -## Issue 2: FunctionContext — public mutable grab-bag - -**Severity**: High. 26 public properties, all writable by any code with access to the context object. - -`src/Php/Context/FunctionContext.php` - -```php -public ?SsaBuilder $ssaBuilder = null; // transient analysis state -public array $stableObjects = []; // SSA optimizer state -public array $hoistedProps = []; // SSA optimizer state -public array $unsafeObjectProps = []; // SSA optimizer state -public array $objects = []; // object variable tracking -public array $stdArrays = []; // std container tracking -public array $stdContainers = []; // std container tracking -public array $localVars = []; // local variable table -public array $staticVars = []; // static variable table -public array $globalVars = []; // global variable table -public array $ceWrappers = []; // class entry wrappers -public int $tmpVarIndex = 0; // auto-increment counter -public array $arguments = []; // function arguments -public bool $inLoop = false; // control-flow state -public bool $inClosure = false; // control-flow state -public bool $hasMultiLevelBreak = false; -public bool $hasMultiLevelContinue = false; -public bool $inAssignExpr = false; // expression context -public array $beforeStmtLines = []; // deferred code (flushed before stmts) -public array $afterStmtLines = []; // deferred code (flushed after stmts) -public array $objectProps; // (uninitialized!) -public array $staticPropRefs = []; // static property references -public int $scopeLevel = 0; // lexical scope depth -/** @var array */ -public array $scopeLayouts = []; // per-scope data -``` - -**Issues**: -- Traits directly mutate deeply nested state: `$this->context->stdArrays[$var] = ...`, `$this->context->localVars[$name] = ...` -- No semantic grouping — analysis state, variable tracking, control-flow flags all mixed -- `$objectProps` is declared but never initialized (could be null at runtime) -- `$scopeLayouts` is managed through `enterScope()`/`leaveScope()` — but can be bypassed -- `$tmpVarIndex` auto-increment — should use a method instead of direct `++` - -**Recommendation**: -- Group related properties into sub-objects: `VariableTable`, `ControlFlowState`, `ScopeManager` -- Make properties that should only be read by the compiler layer private/protected with getters -- Add `incrementTmpVar(): int`, `addLocalVar()`, `addBeforeStmt()` methods -- Initialize `$objectProps = []` - ---- - -## Issue 3: CompilerBase — only 1.9% private methods - -**Severity**: Medium. Virtually everything is public or protected. - -`src/Php/CompilerBase.php` — 269 methods total: -- ~25 public methods (many should be protected or internal) -- ~239 protected methods (most should be private — internal helpers) -- **5 private methods** (1.9%) - -### 3.1 Methods that should be private - -The following methods are internal helpers only called from within CompilerBase (not from Preprocessor, Translator, or traits). They are unnecessarily `protected`: - -| Method | Line | Called from | -|--------|------|-------------| -| `resetFunction()` | 833 | Internal only | -| `resetMethod()` | 840 | Internal only | -| `resetClass()` | 846 | Internal only | -| `resolveObjectClassDef()` | 816 | Already private ✓ | -| `getBigIntLiteralString()` | 1183 | Already private ✓ | -| `getDecimalLiteralString()` | 1188 | Already private ✓ | -| `parseBeforeStmtLines()` | 1321 | Internal, but accessed by traits | -| `parseAfterStmtLines()` | 1331 | Internal, but accessed by traits | -| `genTmpVarName()` | 734 | Public — should at least be protected | - -### 3.2 Public methods that are internal concern - -| Method | Current visibility | Issue | -|--------|-------------------|-------| -| `genTmpVarName()` | public | Only used internally for variable name generation | -| `writeFile()` | public | File I/O — should be a separate service | -| `stop()` | public | Error helper — could be internal | -| `isScalarInt()` | public | AST helper, only used internally | -| `getType()` | public | AST helper, only used internally | -| `getObjectType()` | public | Type mapping, used internally | -| `getTypeFromZendType()` | public | Type mapping, used internally | -| `getIncludeDir()` | public | Config getter — should be on a Config object | -| `getBuildDir()` | public | Config getter — should be on a Config object | - -### 3.3 Public constants leaked as API - -30 public constants for internal type names, literal values, etc. These are needed by traits but expose internal naming conventions. - ---- - -## Issue 4: Trait → Context coupling - -**Severity**: Medium. Traits bypass any encapsulation boundary and directly mutate `$this->context`. - -| Trait | `$this->context->` accesses | -|-------|---------------------------| -| `StdContainerTrait` | 40+ accesses to `stdArrays`, `stdContainers`, `objects`, `localVars` | -| `LoopVarOptimizer` | accesses to `localVars`, `arguments`, `scopeLevel`, `inLoop` | -| `SsaPropOptimizer` | accesses to `stableObjects`, `hoistedProps`, `unsafeObjectProps`, `objects` | -| `FuncCallOptimizer` | accesses to `beforeStmtLines`, `arguments`, `localVars` | -| `SsaTypeOptimizer` | accesses to `localVars` | -| `BinaryOpTrait` | accesses to `objects`, `localVars` | - -**Issues**: -- Traits have no declared contract — they assume `$this->context` exists and has specific properties -- If a property name changes in FunctionContext, all 6 traits break silently -- No type safety — arrays are indexed by string but accessed with arbitrary keys - -**Recommendation**: -- Define a `ContextAccess` interface that traits must use instead of direct property access -- Or: inject context into trait methods as a parameter instead of reading from `$this` -- Short-term: add `@property-read` annotations to document the contract - ---- - -## Issue 5: Preprocessor directly mutates entity state - -**Severity**: Medium. Preprocessor bypasses entity boundaries. - -`src/Php/Preprocessor.php`: -```php -line 279: $argInfo->name = $name; // direct property set -line 329: $functionDef->argInfoList[] = $argInfo; // direct array append -line 411: $this->methodDef->functionDef = $functionDef; // direct property set -line 447: $this->classDef->extends = $this->parentClass; // direct property set -line 585: $this->classDef->constants[$constInfo->name] = ...; // direct array set -line 618: $this->classDef->properties[$name] = $propDef; // direct array set -``` - -**Recommendation**: Use entity methods: `$functionDef->addArg($argInfo)`, `$this->methodDef->setFunctionDef($functionDef)`, `$classDef->addProperty($propDef)`, etc. - ---- - -## Issue 6: CompilerBase protected state leaked to inheritance chain - -**Severity**: Low-Medium. The chain CompilerBase → Preprocessor → Translator means any protected property in CompilerBase is accessible from Translator. - -CompilerBase has ~50 protected properties. Translator is 3301 lines and accesses many of them. There's no way to know which properties are "safe to use" vs "internal to CompilerBase." - -**Recommendation**: Migrate internal-only properties to `private` over time, with explicit getter methods where needed. - ---- - -## Issue 7: Platform/Backend — well encapsulated - -**Severity**: None. The Platform and Backend layers are well-encapsulated: -- All state is private (e.g., `$compilerCommand`, `$linkerCommand` in GccLikeBackend) -- Only methods are public -- Abstract contracts are clear -- Factory pattern is used consistently - -**No changes needed in this layer.** - ---- - -## Issue 8: ScopeContext is dead code - -**Severity**: Low. `src/Php/Context/ScopeContext.php` — 7 lines, empty class body. Used as a placeholder type in FunctionContext's `$scopeLayouts` array. Either populate it or remove it. - ---- - -## Implementation Priority - -| # | Issue | Impact | Effort | Risk | -|---|-------|--------|--------|------| -| 1.1 | Entity: readonly for constructor fields | Data integrity | 2h | Low | -| 1.2 | Entity: add mutation methods (addArg, addProperty, etc.) | Safe mutation | 3h | Medium | -| 2.1 | FunctionContext: group properties into sub-objects | Clarity | 4h | Medium | -| 2.2 | FunctionContext: add accessor methods | Controlled mutation | 3h | Medium | -| 3 | CompilerBase: demote public→protected, protected→private | Boundary clarity | 4h | Medium | -| 4 | Define trait context contract | Safe coupling | 3h | Medium | -| 5 | Preprocessor: use entity methods | Consistent mutation | 2h | Low | -| 8 | Remove ScopeContext dead code | Cleanup | 15m | None | - -**Recommended order**: Start with 8 (quick win), then 1.1 + 1.2 (entity cleanup), then 2.1 + 2.2 (context cleanup), then 3 + 5 + 4 (CompilerBase boundary). diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6cf58c8f..3d843be0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -32,6 +32,12 @@ jobs: - name: Checkout TypePHP uses: actions/checkout@v4 + - name: Checkout phpy + uses: actions/checkout@v4 + with: + repository: swoole/phpy + path: third_party/phpy + - name: Setup PHP uses: shivammathur/setup-php@v2 with: @@ -46,7 +52,7 @@ jobs: - name: Install native build dependencies run: | sudo apt-get update - sudo apt-get install --yes build-essential cmake libgmp-dev libmpfr-dev pkg-config + sudo apt-get install --yes build-essential cmake libgmp-dev libmpfr-dev pkg-config python3-dev - name: Install Composer dependencies run: composer install --prefer-dist --no-progress @@ -62,6 +68,14 @@ jobs: cmake --build "${PHPX_HOME}/build" --target phpx --parallel 2 test -f "${PHPX_HOME}/lib/libphpx.so" + - name: Build phpy + working-directory: third_party/phpy + run: | + phpize + ./configure + make -j2 + test -f modules/phpy.so + - name: Upload PHPX library uses: actions/upload-artifact@v4 with: @@ -70,6 +84,14 @@ jobs: retention-days: 1 path: vendor/swoole/phpx/lib/libphpx.so + - name: Upload phpy extension + uses: actions/upload-artifact@v4 + with: + name: phpy-php-${{ matrix.php }} + if-no-files-found: error + retention-days: 1 + path: third_party/phpy/modules/phpy.so + phpunit: name: PHPUnit (PHP ${{ matrix.php }}) runs-on: ubuntu-latest @@ -101,7 +123,7 @@ jobs: - name: Install native build dependencies run: | sudo apt-get update - sudo apt-get install --yes build-essential cmake libgmp-dev libmpfr-dev pkg-config + sudo apt-get install --yes build-essential cmake libgmp-dev libmpfr-dev pkg-config python3-dev - name: Install Composer dependencies run: composer install --prefer-dist --no-progress @@ -112,6 +134,21 @@ jobs: name: libphpx-php-${{ matrix.php }} path: vendor/swoole/phpx/lib + - name: Download phpy extension + uses: actions/download-artifact@v4 + with: + name: phpy-php-${{ matrix.php }} + path: third_party/phpy/modules + + - name: Enable phpy extension + shell: bash + run: | + php_ini_dir="$(php-config --ini-dir)" + echo "extension=${GITHUB_WORKSPACE}/third_party/phpy/modules/phpy.so" \ + | sudo tee "${php_ini_dir}/90-phpy.ini" + echo "PHP_INI_SCAN_DIR=${php_ini_dir}" >> "${GITHUB_ENV}" + php --ri phpy + - name: Configure native library path shell: bash run: echo "LD_LIBRARY_PATH=${PHPX_HOME}/lib:$(php-config --prefix)/lib" >> "${GITHUB_ENV}" @@ -152,7 +189,7 @@ jobs: - name: Install native build dependencies run: | sudo apt-get update - sudo apt-get install --yes build-essential cmake libgmp-dev libmpfr-dev pkg-config + sudo apt-get install --yes build-essential cmake libgmp-dev libmpfr-dev pkg-config python3-dev - name: Install Composer dependencies run: composer install --prefer-dist --no-progress @@ -163,12 +200,33 @@ jobs: name: libphpx-php-${{ matrix.php }} path: vendor/swoole/phpx/lib + - name: Download phpy extension + uses: actions/download-artifact@v4 + with: + name: phpy-php-${{ matrix.php }} + path: third_party/phpy/modules + + - name: Enable phpy extension + shell: bash + run: | + php_ini_dir="$(php-config --ini-dir)" + echo "extension=${GITHUB_WORKSPACE}/third_party/phpy/modules/phpy.so" \ + | sudo tee "${php_ini_dir}/90-phpy.ini" + echo "PHP_INI_SCAN_DIR=${php_ini_dir}" >> "${GITHUB_ENV}" + php --ri phpy + - name: Configure native library path shell: bash run: | test -f "${PHPX_HOME}/lib/libphpx.so" echo "LD_LIBRARY_PATH=${PHPX_HOME}/lib:$(php-config --prefix)/lib" >> "${GITHUB_ENV}" + - name: Build bootstrap TypePHP compiler + run: | + php bin/tpc.php project.yml --job 2 --no-progress + test -x ./tpc + ./tpc --version + - name: Show build environment run: | php -v @@ -176,10 +234,11 @@ jobs: cmake --version c++ --version - - name: Run compiler PHPT suite + - name: Run compiler PHPT suite with bootstrap compiler run: | mkdir -p build - php run-tests.php -q -j2 -w build/failed-tests.txt -W build/test-results.txt tests/compiler + php run-tests.php -q -j8 --compiler ./tpc \ + -w build/failed-tests.txt -W build/test-results.txt tests/compiler - name: Upload PHPT failure artifacts if: failure() diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index bf27066e..00000000 --- a/CLAUDE.md +++ /dev/null @@ -1,103 +0,0 @@ -# CLAUDE.md - -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. - -## Project Overview - -TypePHP is a PHP native compilation project. Its `tpc` command is TypePHP Compiler (AOT), which translates PHP source code to C++, then compiles it with GCC/Clang/MSVC into native binaries. It supports Linux (primary), macOS, and Windows. - -**Prerequisites**: PHP 8.2+, GCC 9+ (C++17), CMake 3.24+. The `swoole/phpx` extension must be compiled (see README.md). - -## TypePHP Language Design Principles - -TypePHP should not blindly mirror every PHP language behavior. Most PHP syntax and semantics should remain compatible with ZendPHP, but some legal PHP constructs are historical baggage or language-design mistakes that conflict with static compilation, clear semantics, or robust generated C++ code. - -When reviewing or changing compiler behavior: - -- Prefer PHP compatibility for common, well-defined syntax that does not weaken the TypePHP static model. -- Reject PHP historical baggage when the syntax is ambiguous, surprising, or only preserved for legacy compatibility. -- Diagnose such cases as early as possible during preprocessing/static compilation, instead of deferring to runtime TypeCheck or ZendVM errors. -- Provide precise errors that include the relevant function/method name, parameter/property name, and type information where applicable. -- Compare with other statically compiled languages such as C/C++, Java, C#, Go, Rust, Kotlin, and TypeScript before deciding whether TypePHP should preserve or reject a PHP behavior. - -Example: `function test($a = 1, $b, $c) {}` is legal in PHP, but the default value for `$a` is effectively ignored and all parameters become required. This is a PHP historical compatibility artifact. TypePHP should reject it during preprocessing instead of preserving the behavior. - -Example: PHP permits `return $value;` inside `__construct()` and lets callers consume `parent::__construct()` as a value, even though constructors cannot declare a return type. TypePHP treats constructors consistently with C++/Java-style semantics: constructors initialize objects and must not return values. `return;` is allowed, but `return $value;` or using a constructor call as a value must be rejected during static compilation. - -## Build & Test Commands - -```bash -# Install PHP dependencies -composer install - -# Compile a PHP project to a native binary -./tpc - -# Run all PHPUnit tests -./vendor/bin/phpunit - -# Run a single PHPUnit test class -./vendor/bin/phpunit phpunit/src/AstNodeTypeTest.php - -# Run PHPT integration tests (all) -php run-tests.php tests/compiler/ - -# Run a single PHPT test -php run-tests.php tests/compiler/arrays.phpt -``` - -## Architecture - -### Translation Pipeline - -The compiler follows a 4-stage pipeline, orchestrated by `src/Translator.php` (the main entry point): - -1. **prepare()** — Scan PHP files, collect symbol declarations and dependencies, topological-sort for compilation order -2. **convert()** — Parse PHP AST via `nikic/php-parser`, translate each node to C++ source code -3. **compile()** — Invoke the platform C++ compiler (GCC/Clang/MSVC) on generated `.cc` files -4. **build()** — Link object files into a native binary executable - -### Class Hierarchy - -``` -src/CompilerBase.php (core PHP→C++ translation logic, indent/output/mode helpers) - ├─ uses traits: AstNodeType, FuncCallOptimizer, ClosureGenerator, - │ PlaceHolderGenerator, PropertyPromotion, MagicMethodDetector - └─ src/Preprocessor.php (scanning, symbol tables, dependency sort, YAML config) - └─ src/Translator.php (full pipeline: prepare→convert→compile→build) - └─ src/CompilerTest.php (test-only subclass, used by PHPUnit tests) -``` - -### Key Components - -| Directory | Purpose | -|-----------|---------| -| `src/Entity/` | Data classes: `ClassDef`, `FunctionDef`, `MethodDef`, `PropertyDef`, `ConstantDef`, `InterfaceDef` | -| `src/Generator/` | Codegen helpers: `ClosureGenerator`, `PlaceHolderGenerator`, `PropertyPromotion`, `Utils` | -| `src/Backend/` | Compiler abstraction: `CompilerBackend` (abstract) → `Gcc`, `Clang`, `Msvc`. Factory pattern via `CompilerFactory` | -| `src/Platform/` | OS abstraction: `PlatformBase` → `Linux`, `Macos`, `Windows`. Factory via `PlatformFactory` | -| `src/Context/` | `ScopeContext` and `FunctionContext` for variable scoping and type tracking | -| `src/Exception/` | `SyntaxError`, `Unsupported`, `DynamicCall`, `PlaceHolder`, `Skip`, `Redo`, `TestError` | -| `src/Parser/` | Special-purpose parsers like `StdContainerParser` (C++ std container foreach support) | -| `src/Resolver/Reflection.php` | Static helpers wrapping PHP reflection (internal class/function detection) | -| `src/Generator/Symbol.php` | Maps PHP operations to `phpx` C++ API symbol names | -| `src/Build/FileScanner.php` | Recursive file discovery with extension filtering (supports `.php`, `.cpp`, `.c`, `.s`, `.m`, `.mm`) | -| `src/Entity/ArgInfo.php` | Generates C function argument info structures for internal function registration | -| `src/Extractor.php` | Extracts interfaces from PHP classes | -| `src/Transform/Visitor.php` | Base `NodeVisitorAbstract` extension (skeleton for custom AST visitors) | - -### Configuration - -- `project.yml` — per-project build config (name, build-mode, C++ standard, compiler flags, sources, resources/icon) -- Command-line arguments and YAML config are merged in `Preprocessor`, with CLI taking highest priority - -### Generated Output - -Generated `.cc` and `.o` files land in `build/` directory. The compiled binary is named from `project.yml`'s `name` field (default: `app`). - -### Test Infrastructure - -- **PHPUnit tests** (`phpunit/src/`) — unit/integration tests for compiler internals. Bootstrap at `phpunit/bootstrap.php` defines a `BaseTest` class with an `exec()` helper that runs the compiler and expects a `TestError` exception containing a given string -- **PHPT tests** (`tests/compiler/`) — end-to-end tests using the standard PHPT format (`run-tests.php`). Each `.phpt` contains PHP source and expected output sections - -When writing new compiler tests, use `CompilerTest::create(ROOT_PATH)` (in `src/CompilerTest.php`) which sets `forTest = true` to enable test-specific behavior without writing files to disk. diff --git a/LICENSE b/LICENSE index 35e65213..f5054e9e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,10 +1,7 @@ -TypePHP -Copyright (C) 2026 上海识沃网络科技有限公司 - GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 - Copyright (C) 2007 Free Software Foundation, Inc. +Copyright (C) 2026 上海识沃网络科技有限公司. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. diff --git a/bin/extractor.php b/bin/extractor.php index dc9645a2..b479651f 100644 --- a/bin/extractor.php +++ b/bin/extractor.php @@ -48,7 +48,7 @@ function showUsage(): void USAGE; } -function main(array $argv): void +function extractorMain(array $argv): void { // 解析命令行参数 $options = [ @@ -153,5 +153,5 @@ function main(array $argv): void // 运行主函数 if (php_sapi_name() === 'cli') { - main($argv); -} \ No newline at end of file + extractorMain($argv); +} diff --git a/composer.json b/composer.json index 64eb0e8b..457e5aac 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ "require-dev": { "phpunit/phpunit": "^10.4", "friendsofphp/php-cs-fixer": "^3.40", - "phpstan/phpstan": "*" + "phpstan/phpstan": "^2.2" }, "autoload": { "psr-4": { diff --git a/composer.lock b/composer.lock index 0ca142be..933af660 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "0f52f2f55a2120be93f51f4e0003bacc", + "content-hash": "11856d5d63148d99436c5b7ff7f6c866", "packages": [ { "name": "ajaxray/ansikit", diff --git a/phpunit/src/Platform/PlatformTest.php b/phpunit/src/Platform/PlatformTest.php index 3833cbfd..f499bb77 100644 --- a/phpunit/src/Platform/PlatformTest.php +++ b/phpunit/src/Platform/PlatformTest.php @@ -197,6 +197,7 @@ class PlatformTest extends TestCase $config = $platform->getCrtConfig(); $this->assertEquals('/NODEFAULTLIB:LIBCMT', $config); + $this->assertSame('/DLL', $platform->getSharedLinkFlag()); } /** @@ -310,6 +311,8 @@ class PlatformTest extends TestCase $platform = new Linux(); $this->assertEquals('-shared', $platform->getSharedLinkFlag()); + $this->assertSame('', $platform->getSubsystemOptions(true)); + $this->assertSame('', $platform->getCrtConfig()); } /** diff --git a/run-tests.php b/run-tests.php index acd09311..5bd49949 100755 --- a/run-tests.php +++ b/run-tests.php @@ -134,7 +134,7 @@ Options: --compiler Use specified compiler binary (default: ./bin/tpc.php). - For bootstrap testing, use: --compiler ./swoole_compiler + For bootstrap testing, use: --compiler ./tpc --target Select the execution backend: native (default), wasm-component, diff --git a/src/Extractor.php b/src/Extractor.php index c981095e..e3238cfb 100644 --- a/src/Extractor.php +++ b/src/Extractor.php @@ -28,7 +28,7 @@ class Extractor public function extractFunctions(string $filename, array $prefixes = ['php_']): array { if (!file_exists($filename)) { - throw new RuntimeException("文件不存在: {$filename}"); + throw new \RuntimeException("文件不存在: {$filename}"); } $this->info("分析文件: {$filename}"); @@ -82,7 +82,7 @@ class Extractor try { $functions = $this->extractFunctions($file, $prefixes); $allFunctions = array_merge($allFunctions, $functions); - } catch (Exception $e) { + } catch (\Throwable $e) { $this->error("处理文件 {$file} 失败: " . $e->getMessage()); } } @@ -120,7 +120,7 @@ class Extractor $output = shell_exec($cmd); if ($output === null) { - throw new RuntimeException('ctags 执行失败'); + throw new \RuntimeException('ctags 执行失败'); } // 解析 JSON 输出 diff --git a/src/Optimizer/LoopVarOptimizer.php b/src/Optimizer/LoopVarOptimizer.php index 10bd45a5..b7a4bee8 100644 --- a/src/Optimizer/LoopVarOptimizer.php +++ b/src/Optimizer/LoopVarOptimizer.php @@ -87,7 +87,7 @@ trait LoopVarOptimizer } /** - * @param array $safeVars + * @param array $safeVars * @param array, deps?: array}> $candidates */ protected function collectLoopVarCandidates(array $stmts, array $safeVars, array &$candidates): void @@ -154,7 +154,7 @@ trait LoopVarOptimizer } /** - * @param array $safeVars + * @param array $safeVars */ protected function trackLoopSafeAssignment(NodeAbstract $expr, array &$safeVars): void { @@ -191,7 +191,7 @@ trait LoopVarOptimizer } /** - * @param array $safeVars + * @param array $safeVars * @param array, deps?: array}> $candidates */ protected function tryCollectWhilePostDecCandidate(Stmt\While_ $stmt, array $safeVars, array &$candidates): void @@ -216,7 +216,7 @@ trait LoopVarOptimizer } /** - * @param array $safeVars + * @param array $safeVars * @param array, deps?: array}> $candidates */ protected function tryCollectForCounterCandidate(Stmt\For_ $stmt, array $safeVars, array &$candidates): void @@ -294,7 +294,7 @@ trait LoopVarOptimizer } /** - * @param array $safeVars + * @param array $safeVars * @return array{vars: array, intVars: array}|null */ protected function matchLoopBound(NodeAbstract $expr, string $counterName, array $safeVars, int $step): ?array @@ -343,7 +343,7 @@ trait LoopVarOptimizer } /** - * @param array $safeVars + * @param array $safeVars * @return array{nonNegative: bool, inclusiveSafe: bool}|null */ protected function detectLoopIntExprInfo(NodeAbstract $expr, array $safeVars): ?array @@ -433,7 +433,7 @@ trait LoopVarOptimizer } /** - * @param array $safeVars + * @param array $safeVars * @return array */ protected function collectLoopExprSafeIntVars(NodeAbstract $expr, array $safeVars): array diff --git a/src/Parser/MethodCallTrait.php b/src/Parser/MethodCallTrait.php index b2494dd6..38395c54 100644 --- a/src/Parser/MethodCallTrait.php +++ b/src/Parser/MethodCallTrait.php @@ -515,6 +515,10 @@ trait MethodCallTrait $magicMethod = false; $method = $this->methodNameToStr($expr->name, literal: true); + // Keep the statically named method available to every later branch. + // Re-checking isNamedMethod() does not prove the earlier assignment to + // static analyzers and previously left the object path uninitialized. + $methodName = $this->isNamedMethod($expr->name) ? $expr->name->toString() : ''; $pythonFacadeCall = $this->parsePythonNativeFacadeMethodCall($expr, $object); if ($pythonFacadeCall !== null) { @@ -523,7 +527,6 @@ trait MethodCallTrait // Keyword methods are dispatched before all receiver-specific logic. if ($this->isNamedMethod($expr->name)) { - $methodName = $expr->name->toString(); $receiverType = $this->isVarExpr($expr->var) ? $this->getVarType($object) : $this->detectTypeOfExpr($expr->var); if ($receiverType === Type::VOID) { $receiverType = Type::VAR; @@ -592,7 +595,6 @@ trait MethodCallTrait if (($this->isVarExpr($expr->var) || $materializedNativeReceiver) and $this->isNamedMethod($expr->name)) { $type = $this->getVarType($object); if ($class !== '' && $this->isNativeObjectClass($class)) { - $methodName = $expr->name->toString(); // Native objects have their own C++ virtual thunk for an // overridden family; do not let the Zend-object devirtualizer // downgrade this call to the dynamic path. @@ -647,7 +649,6 @@ trait MethodCallTrait } // 引用参数允许方法调用:有class信息走原生调用,无class信息走动态调用 if (!$this->checkArgType($type, Type::OBJECT) and $type !== Type::REF) { - $methodName = $expr->name->toString(); // 非对象类型可使用内置方法 $fn = $this->findUniversalMethodAnyType($type, $methodName); if ($fn) { @@ -711,7 +712,6 @@ trait MethodCallTrait $type = Type::VAR; } if ($type !== Type::VAR && !$this->checkArgType($type, Type::OBJECT)) { - $methodName = $expr->name->toString(); $fn = $this->findUniversalMethodAnyType($type, $methodName); if ($fn) { // Wrap receiver in type conversion for direct_method handlers diff --git a/src/Platform/PlatformBase.php b/src/Platform/PlatformBase.php index dc015368..48d244fe 100644 --- a/src/Platform/PlatformBase.php +++ b/src/Platform/PlatformBase.php @@ -48,6 +48,21 @@ abstract class PlatformBase */ abstract public function getSharedLibraryExtension(): string; + /** + * 获取生成共享库所需的链接器选项 + */ + abstract public function getSharedLinkFlag(): string; + + /** + * 获取无控制台程序的子系统选项;不适用的平台返回空字符串 + */ + abstract public function getSubsystemOptions(bool $noConsole): string; + + /** + * 获取平台 C 运行库链接配置;不适用的平台返回空字符串 + */ + abstract public function getCrtConfig(): string; + /** * 获取路径分隔符 */ diff --git a/src/Platform/UnixPlatform.php b/src/Platform/UnixPlatform.php index d2753c13..76fca93d 100644 --- a/src/Platform/UnixPlatform.php +++ b/src/Platform/UnixPlatform.php @@ -8,6 +8,21 @@ namespace TypePhp\Platform; */ abstract class UnixPlatform extends PlatformBase { + public function getSharedLinkFlag(): string + { + return '-shared'; + } + + public function getSubsystemOptions(bool $noConsole): string + { + return ''; + } + + public function getCrtConfig(): string + { + return ''; + } + public function getTargetExtension(string $buildMode): string { if ($buildMode === 'lib') { diff --git a/src/Platform/Windows.php b/src/Platform/Windows.php index 84db42d5..3527c972 100644 --- a/src/Platform/Windows.php +++ b/src/Platform/Windows.php @@ -98,6 +98,11 @@ class Windows extends PlatformBase return '.dll'; } + public function getSharedLinkFlag(): string + { + return '/DLL'; + } + public function getPathSeparator(): string { return '\\'; diff --git a/tests/compiler/array_method/method.phpt b/tests/compiler/array_method/method.phpt index 2834287f..a39b38a6 100644 --- a/tests/compiler/array_method/method.phpt +++ b/tests/compiler/array_method/method.phpt @@ -101,7 +101,11 @@ function main() Assert::eq($a->product(), array_product($a)); $array = array("Neo", "Morpheus", "Trinity", "Cypher", "Tank"); - Assert::notEq($array->rand(2), array_rand($array, 2)); + $randomKeys = $array->rand(2); + Assert::eq(count($randomKeys), 2); + Assert::true($randomKeys[0] !== $randomKeys[1]); + Assert::true(array_key_exists($randomKeys[0], $array)); + Assert::true(array_key_exists($randomKeys[1], $array)); $array = array(1, 2, 3, 4, 5); Assert::eq($array->reduce('_sum'), array_reduce($array, '_sum'));