From 00fdd419176447ee32cfe77c1dc4cca97cb2ec42 Mon Sep 17 00:00:00 2001 From: Vincenzo Petrucci Date: Sat, 18 Jul 2026 15:09:45 +0200 Subject: [PATCH 1/2] fix(types): validate symbolic parameter defaults --- .../callables/preg_replace_callback.rs | 2 +- src/types/checker/callables/closures.rs | 2 +- src/types/checker/driver/mod.rs | 6 +- src/types/checker/schema/defaults.rs | 172 ++++++++++++++++-- src/types/checker/schema/mod.rs | 2 +- src/types/checker/schema/validation.rs | 2 +- src/types/checker/type_compat/declarations.rs | 46 ++++- tests/codegen/types/enums.rs | 89 +++++++++ tests/error_tests/type_system.rs | 44 +++++ 9 files changed, 337 insertions(+), 28 deletions(-) diff --git a/src/types/checker/builtins/callables/preg_replace_callback.rs b/src/types/checker/builtins/callables/preg_replace_callback.rs index 242a5a1ae9..de5c1af123 100644 --- a/src/types/checker/builtins/callables/preg_replace_callback.rs +++ b/src/types/checker/builtins/callables/preg_replace_callback.rs @@ -93,7 +93,7 @@ fn contextual_closure_sig( callback.span, &format!("Closure parameter ${}", name), )?; - checker.validate_declared_default_type( + checker.validate_resolved_declared_default_type( &declared_ty, default.as_ref(), callback.span, diff --git a/src/types/checker/callables/closures.rs b/src/types/checker/callables/closures.rs index 3d27d1979b..df5405eca8 100644 --- a/src/types/checker/callables/closures.rs +++ b/src/types/checker/callables/closures.rs @@ -98,7 +98,7 @@ impl Checker { span, &format!("Closure parameter ${}", name), )?; - self.validate_declared_default_type( + self.validate_resolved_declared_default_type( &declared_ty, default.as_ref(), span, diff --git a/src/types/checker/driver/mod.rs b/src/types/checker/driver/mod.rs index 2ef98e911a..f5cb23ec3c 100644 --- a/src/types/checker/driver/mod.rs +++ b/src/types/checker/driver/mod.rs @@ -40,7 +40,7 @@ use super::builtin_user_filter::inject_builtin_user_filter; use super::schema::{ build_class_info_recursive, build_enum_info, build_interface_info_recursive, drop_unresolvable_attribute_arg_refs, validate_deferred_class_constants, - validate_deferred_object_defaults, + validate_deferred_declaration_defaults, }; use super::yield_validation::validate_yield_contexts; use super::Checker; @@ -272,8 +272,8 @@ pub(super) fn check_types_impl( } } } - errors.extend(validate_deferred_object_defaults( - &checker, + errors.extend(validate_deferred_declaration_defaults( + &mut checker, &flattened_classes, program, )); diff --git a/src/types/checker/schema/defaults.rs b/src/types/checker/schema/defaults.rs index 17be7dc2db..44ecf97eac 100644 --- a/src/types/checker/schema/defaults.rs +++ b/src/types/checker/schema/defaults.rs @@ -1,25 +1,26 @@ //! Purpose: -//! Revalidates object-typed declaration defaults after class-like schemas are complete. -//! Covers class/interface methods and declared instance/static property defaults. +//! Revalidates declaration defaults that depend on complete class-like schemas. +//! Covers class/interface/enum method parameters and deferred object property compatibility. //! //! Called from: //! - `crate::types::checker::driver::check_types_impl()` after enum schema construction. //! //! Key details: //! - The initial schema pass cannot reliably resolve inheritance or interface relationships. -//! - Only deferred Object-to-Object pairs are revisited; scalar checks stay in the initial pass. +//! - Direct scoped-constant method defaults and Object-to-Object pairs are revisited. +//! - Plain property scoped-constant defaults stay outside this pass until EIR lowering supports them. use crate::errors::CompileError; -use crate::names::php_symbol_key; -use crate::parser::ast::{Expr, Program, StmtKind}; +use crate::names::{php_symbol_key, Name}; +use crate::parser::ast::{Expr, ExprKind, Program, StaticReceiver, StmtKind}; use crate::types::{traits::FlattenedClass, FunctionSig, PhpType}; use super::super::{infer_expr_type_syntactic, Checker}; -/// Validates every object-typed default deferred during class-like schema construction. +/// Validates every declaration default deferred during class-like schema construction. /// Returns all incompatibilities so the driver can aggregate them with other schema errors. -pub(crate) fn validate_deferred_object_defaults( - checker: &Checker, +pub(crate) fn validate_deferred_declaration_defaults( + checker: &mut Checker, flattened_classes: &[FlattenedClass], program: &Program, ) -> Vec { @@ -38,7 +39,7 @@ pub(crate) fn validate_deferred_object_defaults( let StmtKind::InterfaceDecl { name, .. } = &stmt.kind else { continue; }; - let Some(interface_info) = checker.interfaces.get(name) else { + let Some(interface_info) = checker.interfaces.get(name).cloned() else { continue; }; let interface_key = php_symbol_key(name); @@ -53,19 +54,122 @@ pub(crate) fn validate_deferred_object_defaults( let Some(signature) = interface_info.methods.get(method_key) else { continue; }; - validate_signature_object_defaults(checker, signature, "Method", &mut errors); + validate_signature_deferred_defaults( + checker, + signature, + "Method", + Some(name), + &mut errors, + ); + } + for method_key in &interface_info.static_method_order { + let is_declared_here = interface_info + .static_method_declaring_interfaces + .get(method_key) + .is_some_and(|declaring| php_symbol_key(declaring) == interface_key); + if !is_declared_here { + continue; + } + let Some(signature) = interface_info.static_methods.get(method_key) else { + continue; + }; + validate_signature_deferred_defaults( + checker, + signature, + "Method", + Some(name), + &mut errors, + ); } } + normalize_method_default_receivers(checker); + errors } +/// Rewrites relative receivers in stored method defaults to their declaration scope. +/// Defaults are lowered at call sites, whose active class can differ from the declaring class. +fn normalize_method_default_receivers(checker: &mut Checker) { + let class_parents: std::collections::HashMap> = checker + .classes + .iter() + .map(|(name, info)| (name.clone(), info.parent.clone())) + .collect(); + let class_names: Vec = checker.classes.keys().cloned().collect(); + for class_name in class_names { + let Some(class_info) = checker.classes.get_mut(&class_name) else { + continue; + }; + let instance_declaring = class_info.method_declaring_classes.clone(); + for (method_key, signature) in &mut class_info.methods { + let owner = instance_declaring + .get(method_key) + .map(String::as_str) + .unwrap_or(class_name.as_str()); + let parent = class_parents.get(owner).and_then(Option::as_deref); + normalize_signature_default_receivers(signature, owner, parent); + } + let static_declaring = class_info.static_method_declaring_classes.clone(); + for (method_key, signature) in &mut class_info.static_methods { + let owner = static_declaring + .get(method_key) + .map(String::as_str) + .unwrap_or(class_name.as_str()); + let parent = class_parents.get(owner).and_then(Option::as_deref); + normalize_signature_default_receivers(signature, owner, parent); + } + } + + for interface_info in checker.interfaces.values_mut() { + let instance_declaring = interface_info.method_declaring_interfaces.clone(); + for (method_key, signature) in &mut interface_info.methods { + let Some(owner) = instance_declaring.get(method_key) else { + continue; + }; + normalize_signature_default_receivers(signature, owner, None); + } + let static_declaring = interface_info.static_method_declaring_interfaces.clone(); + for (method_key, signature) in &mut interface_info.static_methods { + let Some(owner) = static_declaring.get(method_key) else { + continue; + }; + normalize_signature_default_receivers(signature, owner, None); + } + } +} + +/// Resolves direct `self::`, `static::`, and `parent::` defaults for one stored signature. +fn normalize_signature_default_receivers( + signature: &mut FunctionSig, + owner_class: &str, + parent_class: Option<&str>, +) { + for default in &mut signature.defaults { + let Some(Expr { + kind: ExprKind::ScopedConstantAccess { receiver, .. }, + .. + }) = default + else { + continue; + }; + let resolved = match receiver { + StaticReceiver::Named(_) => None, + StaticReceiver::Self_ | StaticReceiver::Static => Some(owner_class), + StaticReceiver::Parent => parent_class, + }; + if let Some(class_name) = resolved { + *receiver = StaticReceiver::Named(Name::from(class_name.to_string())); + } + } +} + /// Revalidates local method and property defaults for one source-declared class or enum. -fn validate_class_defaults(checker: &Checker, class_name: &str, errors: &mut Vec) { - let Some(class_info) = checker.classes.get(class_name) else { +fn validate_class_defaults(checker: &mut Checker, class_name: &str, errors: &mut Vec) { + let Some(class_info) = checker.classes.get(class_name).cloned() else { return; }; - validate_class_property_defaults(checker, class_name, class_info, errors); + validate_class_property_defaults(checker, class_name, &class_info, errors); for method in &class_info.method_decls { let method_key = php_symbol_key(&method.name); @@ -77,7 +181,13 @@ fn validate_class_defaults(checker: &Checker, class_name: &str, errors: &mut Vec let Some(signature) = signature else { continue; }; - validate_signature_object_defaults(checker, signature, "Method", errors); + validate_signature_deferred_defaults( + checker, + signature, + "Method", + Some(class_name), + errors, + ); } } @@ -137,13 +247,16 @@ fn validate_class_property_defaults( } } -/// Revalidates object defaults for declared parameters in one resolved callable signature. -fn validate_signature_object_defaults( - checker: &Checker, +/// Revalidates schema-dependent defaults for declared parameters in one callable signature. +fn validate_signature_deferred_defaults( + checker: &mut Checker, signature: &FunctionSig, callable_kind: &str, + owner_class: Option<&str>, errors: &mut Vec, ) { + let previous_class = checker.current_class.clone(); + checker.current_class = owner_class.map(str::to_string); for (index, ((param_name, expected_ty), default)) in signature .params .iter() @@ -161,7 +274,7 @@ fn validate_signature_object_defaults( let Some(default) = default.as_ref() else { continue; }; - validate_object_default( + validate_deferred_parameter_default( checker, expected_ty, default, @@ -169,6 +282,29 @@ fn validate_signature_object_defaults( errors, ); } + checker.current_class = previous_class; +} + +/// Resolves a direct scoped-constant default semantically, or rechecks a deferred object pair. +fn validate_deferred_parameter_default( + checker: &mut Checker, + expected_ty: &PhpType, + default: &Expr, + context: &str, + errors: &mut Vec, +) { + if matches!(default.kind, ExprKind::ScopedConstantAccess { .. }) { + if let Err(error) = checker.validate_resolved_declared_default_type( + expected_ty, + Some(default), + default.span, + context, + ) { + errors.extend(error.flatten()); + } + return; + } + validate_object_default(checker, expected_ty, default, context, errors); } /// Checks one deferred default when both its declared and syntactic types are objects. diff --git a/src/types/checker/schema/mod.rs b/src/types/checker/schema/mod.rs index 47c66d69ae..5895601e41 100644 --- a/src/types/checker/schema/mod.rs +++ b/src/types/checker/schema/mod.rs @@ -18,7 +18,7 @@ mod enums; pub(crate) use attribute_refs::drop_unresolvable_attribute_arg_refs; pub(crate) use class_constants::validate_deferred_class_constants; -pub(crate) use defaults::validate_deferred_object_defaults; +pub(crate) use defaults::validate_deferred_declaration_defaults; pub(crate) use interfaces::*; pub(crate) use classes::*; pub(crate) use enums::*; diff --git a/src/types/checker/schema/validation.rs b/src/types/checker/schema/validation.rs index d3ad647eea..8eb097dc0e 100644 --- a/src/types/checker/schema/validation.rs +++ b/src/types/checker/schema/validation.rs @@ -62,7 +62,7 @@ pub(crate) fn build_method_sig( method.params.iter().zip(params.iter()) { if type_ann.is_some() { - checker.validate_schema_declared_default_type( + checker.validate_schema_parameter_default_type( resolved_ty, default.as_ref(), method.span, diff --git a/src/types/checker/type_compat/declarations.rs b/src/types/checker/type_compat/declarations.rs index 963ef1cd1b..5cd8904015 100644 --- a/src/types/checker/type_compat/declarations.rs +++ b/src/types/checker/type_compat/declarations.rs @@ -9,7 +9,7 @@ //! - Rules here define accepted programs, so PHP covariance, inheritance, and extension-specific constraints must stay explicit. use crate::errors::CompileError; -use crate::parser::ast::{Expr, TypeExpr}; +use crate::parser::ast::{Expr, ExprKind, TypeExpr}; use crate::types::{callable_wrapper_sig, ClassInfo, FunctionSig, PhpType}; use super::super::inference::syntactic::infer_expr_type_syntactic; @@ -190,6 +190,28 @@ impl Checker { Ok(()) } + /// Semantically resolves a declaration default when it is a scoped constant access, then + /// validates the resolved type against the declared type. Other defaults keep the syntactic + /// validation used by declarations that do not depend on completed class-like metadata. + pub(crate) fn validate_resolved_declared_default_type( + &mut self, + expected_ty: &PhpType, + default_expr: Option<&Expr>, + span: crate::span::Span, + context: &str, + ) -> Result<(), CompileError> { + let Some(default_expr) = default_expr else { + return Ok(()); + }; + let default_ty = match &default_expr.kind { + ExprKind::ScopedConstantAccess { receiver, name } => { + self.infer_scoped_constant_access(receiver, name, default_expr)? + } + _ => infer_expr_type_syntactic(default_expr), + }; + self.require_compatible_arg_type(expected_ty, &default_ty, span, context) + } + /// Validates a declaration default while class-like schema metadata is still being built. /// Object-to-object checks are deferred because inheritance and interface relationships are /// incomplete during this phase; every other type pair is validated immediately. @@ -210,11 +232,29 @@ impl Checker { self.validate_declared_default_type(expected_ty, default_expr, span, context) } + /// Validates a method parameter default while class-like schemas are being built. + /// Direct scoped constant accesses are deferred until enum cases and class/interface + /// constants are available; other defaults use the existing schema-time validation. + pub(crate) fn validate_schema_parameter_default_type( + &self, + expected_ty: &PhpType, + default_expr: Option<&Expr>, + span: crate::span::Span, + context: &str, + ) -> Result<(), CompileError> { + if default_expr.is_some_and(|default| { + matches!(default.kind, ExprKind::ScopedConstantAccess { .. }) + }) { + return Ok(()); + } + self.validate_schema_declared_default_type(expected_ty, default_expr, span, context) + } + /// Builds the initial parameter type list for a function declaration, resolving type hints, /// validating defaults, and inferring types for untyped parameters. Adds a variadic parameter /// array type, using the declared element type for typed variadics. pub(crate) fn initial_function_param_types( - &self, + &mut self, name: &str, decl: &FnDecl, ) -> Result, CompileError> { @@ -226,7 +266,7 @@ impl Checker { decl.span, &format!("Function '{}' parameter ${}", name, param_name), )?; - self.validate_declared_default_type( + self.validate_resolved_declared_default_type( &declared_ty, decl.defaults.get(idx).and_then(|d| d.as_ref()), decl.span, diff --git a/tests/codegen/types/enums.rs b/tests/codegen/types/enums.rs index 6bb0aeafa8..dcda177fd1 100644 --- a/tests/codegen/types/enums.rs +++ b/tests/codegen/types/enums.rs @@ -52,6 +52,95 @@ fn test_enum_as_promoted_constructor_param_type() { assert_eq!(out, "
hi
"); } +/// Verifies an unused function accepts an enum case as the default for an enum-typed parameter. +#[test] +fn test_unused_function_accepts_enum_case_parameter_default() { + let out = compile_and_run( + r#"value; +} +echo "ok"; +"#, + ); + assert_eq!(out, "ok"); +} + +/// Verifies method and promoted-constructor parameters materialize enum case defaults. +#[test] +fn test_method_and_promoted_constructor_accept_enum_case_defaults() { + let out = compile_and_run( + r#"value; + } +} +$config = new Config(); +echo $config->level->value; +echo ":"; +echo $config->value(); +"#, + ); + assert_eq!(out, "low:high"); +} + +/// Verifies `self::` and `parent::` defaults resolve in their declaring method contexts. +#[test] +fn test_method_enum_case_defaults_resolve_relative_receivers() { + let out = compile_and_run( + r#"value; + } +} +class LevelDefaults { + public const DEFAULT_LEVEL = Level::High; +} +class ChildDefaults extends LevelDefaults { + public function value(Level $level = parent::DEFAULT_LEVEL): string { + return $level->value; + } +} +echo Level::High->value(); +echo ":"; +echo (new ChildDefaults())->value(); +"#, + ); + assert_eq!(out, "low:high"); +} + +/// Verifies closure signatures accept and materialize enum case parameter defaults. +#[test] +fn test_closure_accepts_enum_case_parameter_default() { + let out = compile_and_run( + r#"value; +}; +echo $value(); +"#, + ); + assert_eq!(out, "low"); +} + /// Verifies `Color::tryFrom(99)` returns `null` for an unknown value (with null coalescing to `Color::Red`), /// `Color::cases()` returns all cases, and case index `1` is `Color::Green` by identity. #[test] diff --git a/tests/error_tests/type_system.rs b/tests/error_tests/type_system.rs index 146a5d1694..d59ac7201a 100644 --- a/tests/error_tests/type_system.rs +++ b/tests/error_tests/type_system.rs @@ -462,6 +462,50 @@ class Box { ); } +/// Verifies an enum-typed parameter default rejects a missing enum case semantically. +#[test] +fn test_error_enum_case_parameter_default_rejects_missing_case() { + expect_error( + r#" Date: Sat, 18 Jul 2026 23:01:14 +0200 Subject: [PATCH 2/2] fix(reflection): preserve lexical default constant names --- src/codegen/lower_inst/objects/reflection.rs | 66 +++++++++++++++++++- src/types/checker/schema/interfaces.rs | 1 + src/types/schema.rs | 2 + tests/codegen/oop/attributes.rs | 19 +++++- 4 files changed, 84 insertions(+), 4 deletions(-) diff --git a/src/codegen/lower_inst/objects/reflection.rs b/src/codegen/lower_inst/objects/reflection.rs index d9bb7323fa..8b35c6a501 100644 --- a/src/codegen/lower_inst/objects/reflection.rs +++ b/src/codegen/lower_inst/objects/reflection.rs @@ -1394,6 +1394,7 @@ fn reflection_function_metadata( None, Some(declaring_function), &[], + None, )?; metadata.required_parameter_count = required_parameter_count; metadata.type_metadata = type_metadata; @@ -1429,6 +1430,7 @@ fn reflection_builtin_function_metadata( None, Some(declaring_function), &[], + None, )?; metadata.required_parameter_count = required_parameter_count; metadata.type_metadata = type_metadata; @@ -1716,6 +1718,7 @@ fn reflection_function_parameter_metadata( None, Some(declaring_function), &[], + None, )?; let Some(parameter) = reflection_parameter_member_for_selector(¶meters, selector) else { return Ok(empty_reflection_metadata()); @@ -3232,6 +3235,16 @@ fn reflection_class_method_member( is_deprecated: sig.deprecation.is_some(), is_generator, }; + let source_defaults = declaring_class_name + .as_deref() + .and_then(|declaring_class| { + reflection_source_method_defaults( + ctx, + declaring_class, + &method_key, + flags.is_static, + ) + }); let parameters = reflection_parameter_members_with_declaring_class( ctx, sig, @@ -3240,6 +3253,7 @@ fn reflection_class_method_member( declaring_class_name.as_deref(), Some(declaring_function), &reflection_promoted_constructor_parameter_names(info, &method_key), + source_defaults.as_deref(), )?; Ok(Some(ReflectionListedMember { name: method_key.clone(), @@ -3316,6 +3330,12 @@ fn reflection_interface_method_member( is_deprecated: sig.deprecation.is_some(), is_generator: false, }; + let source_defaults = reflection_source_method_defaults( + ctx, + declaring_class_name.as_str(), + &method_key, + is_static, + ); let parameters = reflection_parameter_members_with_declaring_class( ctx, sig, @@ -3324,6 +3344,7 @@ fn reflection_interface_method_member( Some(declaring_class_name.as_str()), Some(declaring_function), &[], + source_defaults.as_deref(), )?; Ok(Some(ReflectionListedMember { name: method_key, @@ -3404,6 +3425,7 @@ fn reflection_trait_method_member( Some(trait_name), Some(declaring_function), &[], + None, )?; Ok(Some(ReflectionListedMember { name: method_key, @@ -3926,6 +3948,39 @@ fn reflection_promoted_constructor_parameter_names( } } +/// Returns lexical parameter defaults from the method's declaring class or interface. +/// Semantic signatures may canonicalize relative receivers for runtime materialization, while +/// ReflectionParameter must preserve source-visible names such as `self::X` and `parent::Y`. +fn reflection_source_method_defaults( + ctx: &FunctionContext<'_>, + declaring_class_name: &str, + method_key: &str, + is_static: bool, +) -> Option>> { + let declaring_class_name = declaring_class_name.trim_start_matches('\\'); + let declarations = ctx + .module + .class_infos + .get(declaring_class_name) + .map(|info| info.method_decls.as_slice()) + .or_else(|| { + ctx.module + .interface_infos + .get(declaring_class_name) + .map(|info| info.method_decls.as_slice()) + })?; + let method = declarations.iter().find(|method| { + method.is_static == is_static && php_symbol_key(&method.name) == method_key + })?; + Some( + method + .params + .iter() + .map(|(_, _, default, _)| default.clone()) + .collect(), + ) +} + /// Builds reflected parameter metadata and attaches declaring class metadata when present. fn reflection_parameter_members_with_declaring_class( ctx: &FunctionContext<'_>, @@ -3935,6 +3990,7 @@ fn reflection_parameter_members_with_declaring_class( declaring_class_name: Option<&str>, declaring_function: Option, promoted_parameter_names: &[String], + source_defaults: Option<&[Option]>, ) -> Result> { reflection_parameter_members_with_declaring_function( ctx, @@ -3944,6 +4000,7 @@ fn reflection_parameter_members_with_declaring_class( declaring_class_name, declaring_function, promoted_parameter_names, + source_defaults, ) } @@ -3956,6 +4013,7 @@ fn reflection_parameter_members_with_declaring_function( declaring_class_name: Option<&str>, declaring_function: Option, promoted_parameter_names: &[String], + source_defaults: Option<&[Option]>, ) -> Result> { let mut parameters = Vec::new(); for (index, (name, ty)) in sig.params.iter().enumerate() { @@ -3986,8 +4044,12 @@ fn reflection_parameter_members_with_declaring_function( }) .transpose()? .flatten(); - let default_value_constant_name = - default_expr.and_then(reflection_parameter_default_constant_name); + let source_default_expr = source_defaults + .and_then(|defaults| defaults.get(index)) + .and_then(Option::as_ref); + let default_value_constant_name = source_default_expr + .or(default_expr) + .and_then(reflection_parameter_default_constant_name); let is_array_type = reflection_parameter_has_named_type(type_metadata.as_ref(), "array"); let is_callable_type = reflection_parameter_has_named_type(type_metadata.as_ref(), "callable"); diff --git a/src/types/checker/schema/interfaces.rs b/src/types/checker/schema/interfaces.rs index 411cdd70e0..7df87d0ec9 100644 --- a/src/types/checker/schema/interfaces.rs +++ b/src/types/checker/schema/interfaces.rs @@ -378,6 +378,7 @@ pub(crate) fn build_interface_info_recursive( parents: interface.extends.clone(), properties, property_order, + method_decls: interface.methods.clone(), methods, method_declaring_interfaces, method_order, diff --git a/src/types/schema.rs b/src/types/schema.rs index c0689908a0..aa1d87377a 100644 --- a/src/types/schema.rs +++ b/src/types/schema.rs @@ -216,6 +216,8 @@ pub struct InterfaceInfo { pub parents: Vec, pub properties: HashMap, pub property_order: Vec, + /// Source declarations retained so Reflection can preserve lexical parameter-default names. + pub method_decls: Vec, /// Instance method contracts, keyed by PHP's case-insensitive method key. /// /// These entries are the only methods that participate in interface diff --git a/tests/codegen/oop/attributes.rs b/tests/codegen/oop/attributes.rs index 4b80b40ae7..c0413e6389 100644 --- a/tests/codegen/oop/attributes.rs +++ b/tests/codegen/oop/attributes.rs @@ -3298,7 +3298,12 @@ class ReflectDefaultConstBase { } class ReflectDefaultConstTarget extends ReflectDefaultConstBase { const LABEL = "L"; - public function run($self = self::LABEL, $parent = parent::BASE, $class = self::class, $literal = 7) {} + public function run($self = self::LABEL, $parent = parent::BASE, $named = ReflectDefaultConstTarget::LABEL, $class = self::class, $literal = 7) {} +} +class ReflectDefaultConstChild extends ReflectDefaultConstTarget {} +interface ReflectDefaultConstInterface { + const LABEL = "I"; + public function run($value = self::LABEL); } $params = (new ReflectionMethod(ReflectDefaultConstTarget::class, "run"))->getParameters(); foreach ($params as $param) { @@ -3320,6 +3325,16 @@ echo $direct->isDefaultValueConstant() ? "C:" : "c:"; echo $direct->getDefaultValueConstantName(); echo ":"; echo $direct->getDefaultValue(); +$interface = (new ReflectionMethod(ReflectDefaultConstInterface::class, "run"))->getParameters()[0]; +echo "|interface:"; +echo $interface->getDefaultValueConstantName(); +echo ":"; +echo $interface->getDefaultValue(); +$inherited = (new ReflectionMethod(ReflectDefaultConstChild::class, "run"))->getParameters()[0]; +echo "|inherited:"; +echo $inherited->getDefaultValueConstantName(); +echo ":"; +echo $inherited->getDefaultValue(); "##, ); assert!( @@ -3329,7 +3344,7 @@ echo $direct->getDefaultValue(); ); assert_eq!( out.stdout, - "self:D:C:self::LABEL:L|parent:D:C:parent::BASE:B|class:D:c:null:ReflectDefaultConstTarget|literal:D:c:null:7|direct:C:parent::BASE:B" + "self:D:C:self::LABEL:L|parent:D:C:parent::BASE:B|named:D:C:ReflectDefaultConstTarget::LABEL:L|class:D:c:null:ReflectDefaultConstTarget|literal:D:c:null:7|direct:C:parent::BASE:B|interface:self::LABEL:I|inherited:self::LABEL:L" ); }