Skip to content

Inner class RFC #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 47 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
70c2ebb
Fix typo in GHSA-hgf5-96fm-v528 NEWS entry
bukka Mar 13, 2025
e144c58
Merge branch 'PHP-8.1' into PHP-8.2
bukka Mar 13, 2025
1158a1e
Merge branch 'PHP-8.2' into PHP-8.3
bukka Mar 13, 2025
74911be
Merge branch 'PHP-8.3' into PHP-8.4
bukka Mar 13, 2025
6ec89f0
Merge branch 'PHP-8.4'
bukka Mar 13, 2025
a7d2703
Correct check for maximum string length in JIT helpers
nielsdos Mar 13, 2025
4139381
Fix GH-18037: SEGV Zend/zend_execute.c
nielsdos Mar 13, 2025
f18d0a1
Merge branch 'PHP-8.4'
nielsdos Mar 13, 2025
f4ba356
Merge branch 'PHP-8.3' into PHP-8.4
nielsdos Mar 13, 2025
b6e55d9
Merge branch 'PHP-8.4'
nielsdos Mar 13, 2025
4e4f172
ext/bcmath: Simplify `bc_divide()` code (#17987)
SakiTakamachi Mar 13, 2025
7c9872e
Fixed pointer subtraction for scale (#17986)
SakiTakamachi Mar 14, 2025
fa1effd
Merge branch 'PHP-8.4'
SakiTakamachi Mar 14, 2025
32547f1
ext/bcmath: If the result is `0`, `n_scale` is set to `0`. (#18056)
SakiTakamachi Mar 14, 2025
1c18267
Destroy temporary module classes in reverse order
arnaud-lb Mar 3, 2025
4b9c72f
Merge branch 'PHP-8.3' into PHP-8.4
arnaud-lb Mar 14, 2025
f75dd82
Merge branch 'PHP-8.4'
arnaud-lb Mar 14, 2025
5a8a98e
add new token
withinboredom Mar 8, 2025
716a401
create the new grammar
withinboredom Mar 8, 2025
1388dca
add new ast pieces
withinboredom Mar 8, 2025
c3cc41e
handle modifiers
withinboredom Mar 8, 2025
b9b93e6
handle compiling class declarations
withinboredom Mar 8, 2025
0ac2ef0
add initial implementation;
withinboredom Mar 8, 2025
17bd2cc
get return types working
withinboredom Mar 9, 2025
eef0b95
make ::class work
withinboredom Mar 9, 2025
f6006ad
fix failing test
withinboredom Mar 9, 2025
1b85087
add another class
withinboredom Mar 9, 2025
3d46bc2
add more tests to validate scope resolution keywords
withinboredom Mar 9, 2025
99abdcd
update reflection
withinboredom Mar 9, 2025
72e14ca
enable visibility
withinboredom Mar 9, 2025
0254635
add tests for autoloading
withinboredom Mar 9, 2025
458fca8
properly persist classes in opcache
withinboredom Mar 9, 2025
7a06f63
handle return type visibility enforcement
withinboredom Mar 9, 2025
eea3ad3
fix static member access
withinboredom Mar 11, 2025
d2b3256
move tests
withinboredom Mar 13, 2025
861da61
add more tests and fix access modifiers
withinboredom Mar 13, 2025
bf196a8
add support for lexical scope
withinboredom Mar 13, 2025
05aecfd
handle type visibility
withinboredom Mar 14, 2025
51f2fc5
fix opcache
withinboredom Mar 14, 2025
4fda62b
fix test
withinboredom Mar 14, 2025
2053a50
refine some error messages
withinboredom Mar 14, 2025
5f9db7b
add type check to return type
withinboredom Mar 14, 2025
7376eff
fix tests
withinboredom Mar 14, 2025
2092910
temporarily fix this test -- might want to undo this
withinboredom Mar 14, 2025
5f5d53f
clean up a bit and check readonly
withinboredom Mar 14, 2025
0ff7a33
handle visibility of methods
withinboredom Mar 14, 2025
b62121a
just do not cache -- more trouble than it is worth right now
withinboredom Mar 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ PHP NEWS
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
?? ??? ????, PHP 8.5.0alpha1

- BCMath:
. Simplify `bc_divide()` code. (SakiTakamachi)
. If the result is 0, n_scale is set to 0. (SakiTakamachi)

- CLI:
. Add --ini=diff to print INI settings changed from the builtin default.
(timwolla)
Expand Down
2 changes: 1 addition & 1 deletion Zend/tests/errmsg/errmsg_027.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@ class test {
echo "Done\n";
?>
--EXPECTF--
Fatal error: Class declarations may not be nested in %s on line %d
Fatal error: Class declarations may not be declared inside functions in %s on line %d
4 changes: 4 additions & 0 deletions Zend/zend.h
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,10 @@ struct _zend_class_entry {
HashTable properties_info;
HashTable constants_table;

zend_class_entry *required_scope;
zend_class_entry *lexical_scope;
char required_scope_absolute;

ZEND_MAP_PTR_DEF(zend_class_mutable_data*, mutable_data);
zend_inheritance_cache_entry *inheritance_cache;

Expand Down
23 changes: 10 additions & 13 deletions Zend/zend_API.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include "zend.h"
#include "zend_execute.h"
#include "zend_API.h"
#include "zend_hash.h"
#include "zend_modules.h"
#include "zend_extensions.h"
#include "zend_constants.h"
Expand Down Expand Up @@ -3263,21 +3264,17 @@ ZEND_API zend_result zend_get_module_started(const char *module_name) /* {{{ */
}
/* }}} */

static int clean_module_class(zval *el, void *arg) /* {{{ */
{
zend_class_entry *ce = (zend_class_entry *)Z_PTR_P(el);
int module_number = *(int *)arg;
if (ce->type == ZEND_INTERNAL_CLASS && ce->info.internal.module->module_number == module_number) {
return ZEND_HASH_APPLY_REMOVE;
} else {
return ZEND_HASH_APPLY_KEEP;
}
}
/* }}} */

static void clean_module_classes(int module_number) /* {{{ */
{
zend_hash_apply_with_argument(EG(class_table), clean_module_class, (void *) &module_number);
/* Child classes may reuse structures from parent classes, so destroy in reverse order. */
Bucket *bucket;
ZEND_HASH_REVERSE_FOREACH_BUCKET(EG(class_table), bucket) {
zend_class_entry *ce = Z_CE(bucket->val);
if (ce->type == ZEND_INTERNAL_CLASS && ce->info.internal.module->module_number == module_number) {
zend_hash_del_bucket(EG(class_table), bucket);
}
} ZEND_HASH_FOREACH_END();

}
/* }}} */

Expand Down
1 change: 1 addition & 0 deletions Zend/zend_ast.h
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ enum _zend_ast_kind {
ZEND_AST_YIELD,
ZEND_AST_COALESCE,
ZEND_AST_ASSIGN_COALESCE,
ZEND_AST_INNER_CLASS,

ZEND_AST_STATIC,
ZEND_AST_WHILE,
Expand Down
170 changes: 163 additions & 7 deletions Zend/zend_compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -893,19 +893,20 @@ uint32_t zend_modifier_token_to_flag(zend_modifier_target target, uint32_t token
}
break;
case T_READONLY:
if (target == ZEND_MODIFIER_TARGET_PROPERTY || target == ZEND_MODIFIER_TARGET_CPP) {
if (target == ZEND_MODIFIER_TARGET_PROPERTY || target == ZEND_MODIFIER_TARGET_CPP || target == ZEND_MODIFIER_TARGET_INNER_CLASS) {
return ZEND_ACC_READONLY;
}
break;
case T_ABSTRACT:
if (target == ZEND_MODIFIER_TARGET_METHOD || target == ZEND_MODIFIER_TARGET_PROPERTY) {
if (target == ZEND_MODIFIER_TARGET_METHOD || target == ZEND_MODIFIER_TARGET_PROPERTY || target == ZEND_MODIFIER_TARGET_INNER_CLASS) {
return ZEND_ACC_ABSTRACT;
}
break;
case T_FINAL:
if (target == ZEND_MODIFIER_TARGET_METHOD
|| target == ZEND_MODIFIER_TARGET_CONSTANT
|| target == ZEND_MODIFIER_TARGET_PROPERTY
|| target == ZEND_MODIFIER_TARGET_INNER_CLASS
|| target == ZEND_MODIFIER_TARGET_PROPERTY_HOOK) {
return ZEND_ACC_FINAL;
}
Expand Down Expand Up @@ -943,6 +944,8 @@ uint32_t zend_modifier_token_to_flag(zend_modifier_target target, uint32_t token
member = "parameter";
} else if (target == ZEND_MODIFIER_TARGET_PROPERTY_HOOK) {
member = "property hook";
} else if (target == ZEND_MODIFIER_TARGET_INNER_CLASS) {
member = "inner class";
} else {
ZEND_UNREACHABLE();
}
Expand Down Expand Up @@ -1050,6 +1053,37 @@ uint32_t zend_add_member_modifier(uint32_t flags, uint32_t new_flag, zend_modifi
return 0;
}
}
if (target == ZEND_MODIFIER_TARGET_INNER_CLASS) {
if ((flags & ZEND_ACC_PPP_MASK) && (new_flag & ZEND_ACC_PPP_MASK)) {
zend_throw_exception(zend_ce_compile_error,
"Multiple access type modifiers are not allowed", 0);
return 0;
}

if ((flags & ZEND_ACC_STATIC) || (new_flag & ZEND_ACC_STATIC)) {
zend_throw_exception(zend_ce_compile_error,
"Static inner classes are not allowed", 0);
return 0;
}

if ((flags & ZEND_ACC_PUBLIC_SET) || (new_flag & ZEND_ACC_PUBLIC_SET)) {
zend_throw_exception(zend_ce_compile_error,
"Public(set) inner classes are not allowed", 0);
return 0;
}

if ((flags & ZEND_ACC_PROTECTED_SET) || (new_flag & ZEND_ACC_PROTECTED_SET)) {
zend_throw_exception(zend_ce_compile_error,
"Protected(set) inner classes are not allowed", 0);
return 0;
}

if ((flags & ZEND_ACC_PRIVATE_SET) || (new_flag & ZEND_ACC_PRIVATE_SET)) {
zend_throw_exception(zend_ce_compile_error,
"Private(set) inner classes are not allowed", 0);
return 0;
}
}
return new_flags;
}
/* }}} */
Expand Down Expand Up @@ -1760,8 +1794,45 @@ static uint32_t zend_get_class_fetch_type_ast(zend_ast *name_ast) /* {{{ */
}
/* }}} */

static bool zend_try_compile_const_expr_resolve_class_name(zval *zv, zend_ast *class_ast);

static zend_string *zend_resolve_nested_class_name(zend_ast *ast) /* {{{ */
{
ZEND_ASSERT(ast->kind == ZEND_AST_INNER_CLASS);

zend_ast *outer_class = ast->child[0];
zend_ast *inner_class = ast->child[1];

zend_string *outer_name, *inner_name, *full_name;

if (outer_class->kind == ZEND_AST_INNER_CLASS) {
outer_name = zend_resolve_nested_class_name(outer_class);
} else {
zval outer_class_name;
zend_try_compile_const_expr_resolve_class_name(&outer_class_name, outer_class);
outer_name = Z_STR(outer_class_name);
}

inner_name = zend_ast_get_str(inner_class);

full_name = zend_string_concat3(
ZSTR_VAL(outer_name), ZSTR_LEN(outer_name),
":>", 2,
ZSTR_VAL(inner_name), ZSTR_LEN(inner_name)
);

zend_string_release(outer_name);

return full_name;
}
/* }}} */

static zend_string *zend_resolve_const_class_name_reference(zend_ast *ast, const char *type)
{
if (ast->kind == ZEND_AST_INNER_CLASS) {
return zend_resolve_nested_class_name(ast);
}

zend_string *class_name = zend_ast_get_str(ast);
if (ZEND_FETCH_CLASS_DEFAULT != zend_get_class_fetch_type_ast(ast)) {
zend_error_noreturn(E_COMPILE_ERROR,
Expand Down Expand Up @@ -1792,6 +1863,11 @@ static bool zend_try_compile_const_expr_resolve_class_name(zval *zv, zend_ast *c
uint32_t fetch_type;
zval *class_name;

if (class_ast->kind == ZEND_AST_INNER_CLASS) {
ZVAL_STR(zv, zend_resolve_nested_class_name(class_ast));
return 1;
}

if (class_ast->kind != ZEND_AST_ZVAL) {
return 0;
}
Expand Down Expand Up @@ -2844,10 +2920,42 @@ static inline void zend_set_class_name_op1(zend_op *opline, znode *class_node) /
}
/* }}} */

static void zend_compile_class_ref(znode *result, zend_ast *name_ast, uint32_t fetch_flags);

static void zend_compile_inner_class_ref(znode *result, zend_ast *ast, uint32_t fetch_flags) /* {{{ */
{
zend_ast *outer_class = ast->child[0];
zend_ast *inner_class = ast->child[1];

znode outer_node, inner_node;

// handle nesting
if (outer_class->kind == ZEND_AST_INNER_CLASS) {
zend_compile_inner_class_ref(&outer_node, outer_class, fetch_flags);
} else {
zend_compile_class_ref(&outer_node, outer_class, fetch_flags);
}

if (inner_class->kind == ZEND_AST_ZVAL && Z_TYPE_P(zend_ast_get_zval(inner_class)) == IS_STRING) {
ZVAL_STR(&inner_node.u.constant, zend_string_dup(Z_STR_P(zend_ast_get_zval(inner_class)), 0));
inner_node.op_type = IS_CONST;
} else {
zend_compile_expr(&inner_node, inner_class);
}

zend_emit_op(result, ZEND_FETCH_INNER_CLASS, &outer_node, &inner_node);
}
/* }}} */

static void zend_compile_class_ref(znode *result, zend_ast *name_ast, uint32_t fetch_flags) /* {{{ */
{
uint32_t fetch_type;

if (name_ast->kind == ZEND_AST_INNER_CLASS) {
zend_compile_inner_class_ref(result, name_ast, fetch_flags);
return;
}

if (name_ast->kind != ZEND_AST_ZVAL) {
znode name_node;

Expand Down Expand Up @@ -7316,6 +7424,9 @@ static zend_type zend_compile_typename_ex(
ZEND_TYPE_FULL_MASK(type) |= _ZEND_TYPE_INTERSECTION_BIT;
ZEND_TYPE_FULL_MASK(type) |= _ZEND_TYPE_ARENA_BIT;
}
} else if (ast->kind == ZEND_AST_INNER_CLASS) {
zend_string *name = zend_resolve_nested_class_name(ast);
return (zend_type) ZEND_TYPE_INIT_CLASS(name, /* allow null */ false, 0);
} else {
type = zend_compile_single_typename(ast);
}
Expand Down Expand Up @@ -9057,10 +9168,6 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel)
if (EXPECTED((decl->flags & ZEND_ACC_ANON_CLASS) == 0)) {
zend_string *unqualified_name = decl->name;

if (CG(active_class_entry)) {
zend_error_noreturn(E_COMPILE_ERROR, "Class declarations may not be nested");
}

const char *type = "a class name";
if (decl->flags & ZEND_ACC_ENUM) {
type = "an enum name";
Expand All @@ -9070,7 +9177,51 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel)
type = "a trait name";
}
zend_assert_valid_class_name(unqualified_name, type);
name = zend_prefix_with_ns(unqualified_name);

if (CG(active_class_entry) && CG(active_op_array)->function_name) {
zend_error_noreturn(E_COMPILE_ERROR, "Class declarations may not be declared inside functions");
}

if (CG(active_class_entry)) {
// rename the inner class so we may reference it by name
name = zend_string_concat3(
ZSTR_VAL(CG(active_class_entry)->name), ZSTR_LEN(CG(active_class_entry)->name),
":>", 2,
ZSTR_VAL(unqualified_name), ZSTR_LEN(unqualified_name)
);

// configure the current ce->flags for a nested class. This should only include:
// - final
// - readonly
// - abstract
decl->flags |= decl->attr & ZEND_ACC_FINAL;
if (decl->attr & ZEND_ACC_ABSTRACT) {
decl->flags |= ZEND_ACC_EXPLICIT_ABSTRACT_CLASS;
}
if (decl->attr & ZEND_ACC_READONLY) {
decl->flags |= ZEND_ACC_READONLY_CLASS | ZEND_ACC_NO_DYNAMIC_PROPERTIES;
}

// configure for a nested class. This should only include:
// - public
// - private
// - protected
int propFlags = decl->attr & (ZEND_ACC_PUBLIC|ZEND_ACC_PROTECTED|ZEND_ACC_PRIVATE);
// remove the flags from attrs
decl->attr &= ~(ZEND_ACC_PUBLIC|ZEND_ACC_PROTECTED|ZEND_ACC_PRIVATE|ZEND_ACC_FINAL|ZEND_ACC_ABSTRACT|ZEND_ACC_READONLY);

// if a class is private or protected, we need to require the correct scope
ce->required_scope = propFlags & (ZEND_ACC_PRIVATE|ZEND_ACC_PROTECTED) ? CG(active_class_entry) : NULL;
ce->required_scope_absolute = propFlags & ZEND_ACC_PRIVATE ? true : false;
ce->lexical_scope = CG(active_class_entry);

// ensure the class is treated as a top-level class and not an anon class
toplevel = true;
} else {
name = zend_prefix_with_ns(unqualified_name);
ce->required_scope = NULL;
ce->lexical_scope = NULL;
}
name = zend_new_interned_string(name);
lcname = zend_string_tolower(name);

Expand All @@ -9088,6 +9239,8 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel)
/* Find an anon class name that is not in use yet. */
name = NULL;
lcname = NULL;
ce->required_scope = NULL;
ce->lexical_scope = NULL;
do {
zend_tmp_string_release(name);
zend_tmp_string_release(lcname);
Expand Down Expand Up @@ -11697,6 +11850,9 @@ static void zend_compile_expr_inner(znode *result, zend_ast *ast) /* {{{ */
case ZEND_AST_MATCH:
zend_compile_match(result, ast);
return;
case ZEND_AST_INNER_CLASS:
zend_compile_inner_class_ref(result, ast, ZEND_FETCH_CLASS_EXCEPTION);
return;
default:
ZEND_ASSERT(0 /* not supported */);
}
Expand Down
1 change: 1 addition & 0 deletions Zend/zend_compile.h
Original file line number Diff line number Diff line change
Expand Up @@ -894,6 +894,7 @@ typedef enum {
ZEND_MODIFIER_TARGET_CONSTANT,
ZEND_MODIFIER_TARGET_CPP,
ZEND_MODIFIER_TARGET_PROPERTY_HOOK,
ZEND_MODIFIER_TARGET_INNER_CLASS,
} zend_modifier_target;

/* Used during AST construction */
Expand Down
Loading
Loading