diff --git a/src/CompilerBase.php b/src/CompilerBase.php index 8ac98b30..2b0ac4d5 100644 --- a/src/CompilerBase.php +++ b/src/CompilerBase.php @@ -2382,7 +2382,12 @@ class CompilerBase implements PropertyAccessContext return $this->getNativeName($method, $classDef->namespace, $classDef->name); } - protected function findNativeClassConst(NodeAbstract $expr, string $class, string $const): string|false + protected function findNativeClassConst( + NodeAbstract $expr, + string $class, + string $const, + ?string $accessingClass = null + ): string|false { if (!$this->hasClass($class)) { return false; @@ -2426,7 +2431,12 @@ class CompilerBase implements PropertyAccessContext if ($constDef === null) { return false; } - if ($classDef instanceof ClassDef && !$this->checkAccessible($classDef, $constDef->flags)) { + if ($classDef instanceof ClassDef + && !$this->checkAccessibleByClassName( + $classDef->getNamespacedName(false), + $constDef->flags, + $accessingClass, + )) { $this->fatalError($expr, 'Constant `' . $classDef->getNamespacedName() . '::' . $const . '` is not accessible'); } if ($constDef->type === Type::ARRAY) { @@ -3869,13 +3879,24 @@ class CompilerBase implements PropertyAccessContext return $this->checkAccessibleByClassName($classDef->getNamespacedName(false), $flags); } - protected function checkAccessibleByClassName(string $declaringClass, int $flags): bool + protected function checkAccessibleByClassName( + string $declaringClass, + int $flags, + ?string $accessingClass = null + ): bool { - $scopeClassDef = $this->classDef; - if ($this->functionDef !== null - && $this->functionDef->attributeFactoryScope !== '' - && $this->hasClass($this->functionDef->attributeFactoryScope)) { - $scopeClassDef = $this->getClass($this->functionDef->attributeFactoryScope); + if ($accessingClass !== null) { + $accessingClass = ltrim($accessingClass, '\\'); + $scopeClassDef = $this->hasClass($accessingClass) + ? $this->getClass($accessingClass) + : null; + } else { + $scopeClassDef = $this->classDef; + if ($this->functionDef !== null + && $this->functionDef->attributeFactoryScope !== '' + && $this->hasClass($this->functionDef->attributeFactoryScope)) { + $scopeClassDef = $this->getClass($this->functionDef->attributeFactoryScope); + } } // 私有方法,只能当前的类使用 if ($flags & Modifiers::PRIVATE) { diff --git a/src/Generator/CallArgumentGenerator.php b/src/Generator/CallArgumentGenerator.php index 826516eb..8fd214b4 100644 --- a/src/Generator/CallArgumentGenerator.php +++ b/src/Generator/CallArgumentGenerator.php @@ -65,7 +65,7 @@ trait CallArgumentGenerator continue; } if (!isset($args[$k])) { - if ($argInfo->defaultValue === null) { + if ($argInfo->default === '') { $errorNode = null; foreach ($callArgs as $a) { if ($a instanceof Node\Arg && $a->name) { @@ -76,7 +76,10 @@ trait CallArgumentGenerator $argName = $argInfo->phpName ?: $this->unescapeVarName($argInfo->name); $this->fatalError($errorNode ?? reset($callArgs), 'Named argument `' . $argName . '` is missing default value'); } - $args[$k] = new Node\Arg($argInfo->defaultValue); + // Defaults are resolved in the declaration scope. Re-parsing + // the original AST here would evaluate self/parent/private + // class constants in the caller's scope instead. + $args[$k] = $this->genDefaultArgumentExpr($nativeFunc, $k); } } ksort($args); diff --git a/src/Resolver/ClassConstantValueTrait.php b/src/Resolver/ClassConstantValueTrait.php index a674fd7a..90ec17e5 100644 --- a/src/Resolver/ClassConstantValueTrait.php +++ b/src/Resolver/ClassConstantValueTrait.php @@ -27,7 +27,12 @@ trait ClassConstantValueTrait $namespace = $this->getNamespaceOfClass($currentClass); } $class = $this->getNamespacedClassName($_class, $namespace); - $nativeConst = $this->findNativeClassConst($expr, $class, $name); + $nativeConst = $this->findNativeClassConst( + $expr, + $class, + $name, + $currentClass !== '' ? $currentClass : null, + ); if ($nativeConst and $expr->hasAttribute('nativeConst')) { $constDef = $expr->getAttribute('nativeConst'); if ($constDef->valueExpr !== null) { diff --git a/src/gen_stub.php b/src/gen_stub.php index 1725b61d..e2cb90e8 100755 --- a/src/gen_stub.php +++ b/src/gen_stub.php @@ -5121,7 +5121,12 @@ function parseFunctionLike( } if ($param->default instanceof Expr\ClassConstFetch && $param->default->class->toLowerString() === "self") { - $defaultValue = getTranslator()->getClassConstValue($func, $name->className->name, $param->default->name->name); + $defaultValue = getTranslator()->getClassConstValue( + $func, + $name->className->name, + $param->default->name->name, + ClassInfo::$currentClass, + ); $defaultValue = var_export($defaultValue, true); } elseif ($param->default instanceof String_) { // Keep this as a PHP expression. ArgInfo escapes the expression diff --git a/tests/compiler/const/private-class-const-param-default.phpt b/tests/compiler/const/private-class-const-param-default.phpt new file mode 100644 index 00000000..4c3786c5 --- /dev/null +++ b/tests/compiler/const/private-class-const-param-default.phpt @@ -0,0 +1,54 @@ +--TEST-- +Private and protected class constants are valid method parameter defaults in declaration scope +--FILE-- +show(); + $private->show(suffix: 'named'); + + $protected = new ProtectedConstChild(); + $protected->show(); + $protected->show(suffix: 'named'); +} +?> +--EXPECT-- +int(42) +string(7) "private" +string(7) "default" +int(42) +string(7) "private" +string(5) "named" +int(7) +string(7) "default" +int(7) +string(5) "named"