fix(generator): handle class constant parameter defaults correctly

- Changed defaultValue check to default property comparison in CallArgumentGenerator
- Added genDefaultArgumentExpr call for proper default value resolution
- Updated findNativeClassConst signature to accept accessing class parameter
- Modified checkAccessibleByClassName to use accessing class context
- Updated getClassConstValue calls to pass current class context
- Added comprehensive test case for private class constant parameter defaults
pull/44/head
韩天峰 3 weeks ago
parent 25e5b47482
commit df43b2a1fd
  1. 37
      src/CompilerBase.php
  2. 7
      src/Generator/CallArgumentGenerator.php
  3. 7
      src/Resolver/ClassConstantValueTrait.php
  4. 7
      src/gen_stub.php
  5. 54
      tests/compiler/const/private-class-const-param-default.phpt

@ -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) {

@ -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);

@ -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) {

@ -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

@ -0,0 +1,54 @@
--TEST--
Private and protected class constants are valid method parameter defaults in declaration scope
--FILE--
<?php
class PrivateConstDefaults
{
private const VALUE = 42;
private const LABEL = 'private';
public function show(
int $value = self::VALUE,
string $label = PrivateConstDefaults::LABEL,
string $suffix = 'default',
): void {
var_dump($value, $label, $suffix);
}
}
class ProtectedConstParent
{
protected const VALUE = 7;
}
class ProtectedConstChild extends ProtectedConstParent
{
public function show(int $value = parent::VALUE, string $suffix = 'default'): void
{
var_dump($value, $suffix);
}
}
function main(): void
{
$private = new PrivateConstDefaults();
$private->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"
Loading…
Cancel
Save