Skip to content

Commit b4354c5

Browse files
committed
allow for defining nested classes
Now we can define nested classes inside other classes
1 parent 8ddc013 commit b4354c5

File tree

5 files changed

+177
-15
lines changed

5 files changed

+177
-15
lines changed

Zend/zend_compile.c

Lines changed: 161 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
#include "zend_call_stack.h"
3939
#include "zend_frameless_function.h"
4040
#include "zend_property_hooks.h"
41+
#include "zend_namespaces.h"
4142

4243
#define SET_NODE(target, src) do { \
4344
target ## _type = (src)->op_type; \
@@ -424,6 +425,7 @@ void zend_init_compiler_data_structures(void) /* {{{ */
424425
zend_stack_init(&CG(delayed_oplines_stack), sizeof(zend_op));
425426
zend_stack_init(&CG(short_circuiting_opnums), sizeof(uint32_t));
426427
CG(active_class_entry) = NULL;
428+
CG(nested_class_queue) = NULL;
427429
CG(in_compilation) = 0;
428430
CG(skip_shebang) = 0;
429431

@@ -1178,6 +1180,60 @@ static zend_string *zend_resolve_const_name(zend_string *name, uint32_t type, bo
11781180
name, type, is_fully_qualified, 1, FC(imports_const));
11791181
}
11801182

1183+
static zend_string *get_namespace_from_scope(const zend_class_entry *scope)
1184+
{
1185+
ZEND_ASSERT(scope != NULL);
1186+
while (scope && scope->lexical_scope && scope->type != ZEND_NAMESPACE_CLASS) {
1187+
scope = scope->lexical_scope;
1188+
}
1189+
return zend_string_copy(scope->name);
1190+
}
1191+
1192+
static zend_string *get_scoped_name(zend_string *ns, zend_string *name)
1193+
{
1194+
name = zend_string_tolower(name);
1195+
if (ns && ZSTR_LEN(ns) && ZSTR_LEN(name) > ZSTR_LEN(ns) + 1 &&
1196+
memcmp(ZSTR_VAL(name), ZSTR_VAL(ns), ZSTR_LEN(ns)) == 0 &&
1197+
ZSTR_VAL(name)[ZSTR_LEN(ns)] == '\\') {
1198+
zend_string *ret = zend_string_init(ZSTR_VAL(name) + ZSTR_LEN(ns) + 1, ZSTR_LEN(name) - ZSTR_LEN(ns) - 1, 0);
1199+
zend_string_release(name);
1200+
return ret;
1201+
}
1202+
return name;
1203+
}
1204+
1205+
zend_string *zend_resolve_class_in_scope(zend_string *name, const zend_class_entry *scope)
1206+
{
1207+
zend_string *ns_name = get_namespace_from_scope(scope);
1208+
zend_string *original_suffix = get_scoped_name(ns_name, name);
1209+
zend_string_release(ns_name);
1210+
1211+
const zend_class_entry *current_scope = scope;
1212+
1213+
while (current_scope && current_scope->type != ZEND_NAMESPACE_CLASS) {
1214+
zend_string *try_name = zend_string_concat3(
1215+
ZSTR_VAL(current_scope->name), ZSTR_LEN(current_scope->name),
1216+
"\\", 1,
1217+
ZSTR_VAL(original_suffix), ZSTR_LEN(original_suffix));
1218+
1219+
zend_string *lc_try_name = zend_string_tolower(try_name);
1220+
1221+
bool has_seen = zend_have_seen_symbol(lc_try_name, ZEND_SYMBOL_CLASS);
1222+
zend_string_release(lc_try_name);
1223+
1224+
if (has_seen) {
1225+
zend_string_release(original_suffix);
1226+
return try_name;
1227+
}
1228+
zend_string_release(try_name);
1229+
1230+
current_scope = current_scope->lexical_scope;
1231+
}
1232+
1233+
zend_string_release(original_suffix);
1234+
return NULL;
1235+
}
1236+
11811237
static zend_string *zend_resolve_class_name(zend_string *name, uint32_t type) /* {{{ */
11821238
{
11831239
char *compound;
@@ -1236,6 +1292,13 @@ static zend_string *zend_resolve_class_name(zend_string *name, uint32_t type) /*
12361292
}
12371293
}
12381294

1295+
if (CG(active_class_entry)) {
1296+
zend_string *nested_name = zend_resolve_class_in_scope(name, CG(active_class_entry));
1297+
if (nested_name) {
1298+
return nested_name;
1299+
}
1300+
}
1301+
12391302
/* If not fully qualified and not an alias, prepend the current namespace */
12401303
return zend_prefix_with_ns(name);
12411304
}
@@ -9014,10 +9077,9 @@ static void zend_compile_use_trait(zend_ast *ast) /* {{{ */
90149077
}
90159078
/* }}} */
90169079

9017-
static void zend_compile_implements(zend_ast *ast) /* {{{ */
9080+
static void zend_compile_implements(zend_ast *ast, zend_class_entry *ce) /* {{{ */
90189081
{
90199082
zend_ast_list *list = zend_ast_get_list(ast);
9020-
zend_class_entry *ce = CG(active_class_entry);
90219083
zend_class_name *interface_names;
90229084
uint32_t i;
90239085

@@ -9075,6 +9137,42 @@ static void zend_compile_enum_backing_type(zend_class_entry *ce, zend_ast *enum_
90759137
zend_type_release(type, 0);
90769138
}
90779139

9140+
static void zend_defer_class_decl(zend_ast *ast)
9141+
{
9142+
ZEND_ASSERT(CG(active_class_entry));
9143+
9144+
if (CG(active_op_array)->function_name) {
9145+
zend_error_noreturn(E_COMPILE_ERROR, "Class declarations may not be declared inside functions");
9146+
}
9147+
9148+
if (CG(nested_class_queue) == NULL) {
9149+
ALLOC_HASHTABLE(CG(nested_class_queue));
9150+
zend_hash_init(CG(nested_class_queue), 8, NULL, ZVAL_PTR_DTOR, 0);
9151+
}
9152+
9153+
zend_hash_next_index_insert_ptr(CG(nested_class_queue), ast);
9154+
}
9155+
9156+
static void zend_scan_nested_class_decl(zend_ast *ast, const zend_string *prefix)
9157+
{
9158+
const zend_ast_list *list = zend_ast_get_list(ast);
9159+
for (int i = 0; i < list->children; i++) {
9160+
ast = list->child[i];
9161+
if (ast->kind == ZEND_AST_CLASS) {
9162+
const zend_ast_decl *decl = (zend_ast_decl *)ast;
9163+
zend_string *name = zend_string_concat3(
9164+
ZSTR_VAL(prefix), ZSTR_LEN(prefix),
9165+
"\\", 1,
9166+
ZSTR_VAL(decl->name), ZSTR_LEN(decl->name));
9167+
zend_string *lc_name = zend_string_tolower(name);
9168+
zend_register_seen_symbol(lc_name, ZEND_SYMBOL_CLASS);
9169+
zend_string_release(lc_name);
9170+
zend_scan_nested_class_decl(decl->child[2], name);
9171+
zend_string_release(name);
9172+
}
9173+
}
9174+
}
9175+
90789176
static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel) /* {{{ */
90799177
{
90809178
zend_ast_decl *decl = (zend_ast_decl *) ast;
@@ -9091,10 +9189,6 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel)
90919189
if (EXPECTED((decl->flags & ZEND_ACC_ANON_CLASS) == 0)) {
90929190
zend_string *unqualified_name = decl->name;
90939191

9094-
if (CG(active_class_entry)) {
9095-
zend_error_noreturn(E_COMPILE_ERROR, "Class declarations may not be nested");
9096-
}
9097-
90989192
const char *type = "a class name";
90999193
if (decl->flags & ZEND_ACC_ENUM) {
91009194
type = "an enum name";
@@ -9104,7 +9198,34 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel)
91049198
type = "a trait name";
91059199
}
91069200
zend_assert_valid_class_name(unqualified_name, type);
9107-
name = zend_prefix_with_ns(unqualified_name);
9201+
9202+
if (CG(active_class_entry)) {
9203+
name = zend_string_concat3(
9204+
ZSTR_VAL(CG(active_class_entry)->name), ZSTR_LEN(CG(active_class_entry)->name),
9205+
"\\", 1,
9206+
ZSTR_VAL(unqualified_name), ZSTR_LEN(unqualified_name));
9207+
9208+
/* configure the class from the modifiers */
9209+
decl->flags |= decl->attr & ZEND_ACC_FINAL;
9210+
if (decl->attr & ZEND_ACC_ABSTRACT) {
9211+
decl->flags |= ZEND_ACC_EXPLICIT_ABSTRACT_CLASS;
9212+
}
9213+
if (decl->attr & ZEND_ACC_READONLY) {
9214+
decl->flags |= ZEND_ACC_READONLY_CLASS | ZEND_ACC_NO_DYNAMIC_PROPERTIES;
9215+
}
9216+
9217+
int propFlags = decl->attr & ZEND_ACC_PPP_MASK;
9218+
decl->attr &= ~(ZEND_ACC_PPP_MASK | ZEND_ACC_FINAL | ZEND_ACC_READONLY | ZEND_ACC_ABSTRACT);
9219+
9220+
ce->required_scope = propFlags & (ZEND_ACC_PRIVATE | ZEND_ACC_PROTECTED) ? CG(active_class_entry) : NULL;
9221+
ce->required_scope_absolute = propFlags & ZEND_ACC_PRIVATE ? true : false;
9222+
ce->lexical_scope = CG(active_class_entry);
9223+
} else {
9224+
name = zend_prefix_with_ns(unqualified_name);
9225+
ce->required_scope = NULL;
9226+
ce->lexical_scope = zend_resolve_namespace(FC(current_namespace));
9227+
}
9228+
91089229
name = zend_new_interned_string(name);
91099230
lcname = zend_string_tolower(name);
91109231

@@ -9122,6 +9243,8 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel)
91229243
/* Find an anon class name that is not in use yet. */
91239244
name = NULL;
91249245
lcname = NULL;
9246+
ce->required_scope = NULL;
9247+
ce->lexical_scope = CG(active_class_entry) ? CG(active_class_entry) : zend_resolve_namespace(FC(current_namespace));
91259248
do {
91269249
zend_tmp_string_release(name);
91279250
zend_tmp_string_release(lcname);
@@ -9163,16 +9286,16 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel)
91639286
zend_resolve_const_class_name_reference(extends_ast, "class name");
91649287
}
91659288

9289+
if (implements_ast) {
9290+
zend_compile_implements(implements_ast, ce);
9291+
}
9292+
91669293
CG(active_class_entry) = ce;
91679294

91689295
if (decl->child[3]) {
91699296
zend_compile_attributes(&ce->attributes, decl->child[3], 0, ZEND_ATTRIBUTE_TARGET_CLASS, 0);
91709297
}
91719298

9172-
if (implements_ast) {
9173-
zend_compile_implements(implements_ast);
9174-
}
9175-
91769299
if (ce->ce_flags & ZEND_ACC_ENUM) {
91779300
if (enum_backing_type_ast != NULL) {
91789301
zend_compile_enum_backing_type(ce, enum_backing_type_ast);
@@ -9181,6 +9304,9 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel)
91819304
zend_enum_register_props(ce);
91829305
}
91839306

9307+
if (ce->lexical_scope->type == ZEND_NAMESPACE_CLASS) {
9308+
zend_scan_nested_class_decl(stmt_ast, name);
9309+
}
91849310
zend_compile_stmt(stmt_ast);
91859311

91869312
/* Reset lineno for final opcodes and errors */
@@ -9190,8 +9316,6 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel)
91909316
zend_verify_abstract_class(ce);
91919317
}
91929318

9193-
CG(active_class_entry) = original_ce;
9194-
91959319
if (toplevel) {
91969320
ce->ce_flags |= ZEND_ACC_TOP_LEVEL;
91979321
}
@@ -9212,7 +9336,7 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel)
92129336
&& !zend_compile_ignore_class(parent_ce, ce->info.user.filename)) {
92139337
if (zend_try_early_bind(ce, parent_ce, lcname, NULL)) {
92149338
zend_string_release(lcname);
9215-
return;
9339+
goto compile_nested_classes;
92169340
}
92179341
}
92189342
} else if (EXPECTED(zend_hash_add_ptr(CG(class_table), lcname, ce) != NULL)) {
@@ -9221,7 +9345,7 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel)
92219345
zend_inheritance_check_override(ce);
92229346
ce->ce_flags |= ZEND_ACC_LINKED;
92239347
zend_observer_class_linked_notify(ce, lcname);
9224-
return;
9348+
goto compile_nested_classes;
92259349
} else {
92269350
goto link_unbound;
92279351
}
@@ -9291,6 +9415,24 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel)
92919415
opline->result.opline_num = -1;
92929416
}
92939417
}
9418+
compile_nested_classes:
9419+
9420+
if (CG(nested_class_queue) == NULL) {
9421+
CG(active_class_entry) = original_ce;
9422+
return;
9423+
}
9424+
9425+
HashTable *queue = CG(nested_class_queue);
9426+
CG(nested_class_queue) = NULL;
9427+
9428+
ZEND_HASH_FOREACH_PTR(queue, ast) {
9429+
zend_compile_class_decl(NULL, ast, true);
9430+
} ZEND_HASH_FOREACH_END();
9431+
9432+
CG(active_class_entry) = original_ce;
9433+
9434+
zend_hash_destroy(queue);
9435+
FREE_HASHTABLE(queue);
92949436
}
92959437
/* }}} */
92969438

@@ -11581,6 +11723,10 @@ static void zend_compile_stmt(zend_ast *ast) /* {{{ */
1158111723
zend_compile_use_trait(ast);
1158211724
break;
1158311725
case ZEND_AST_CLASS:
11726+
if (CG(active_class_entry)) {
11727+
zend_defer_class_decl(ast);
11728+
break;
11729+
}
1158411730
zend_compile_class_decl(NULL, ast, 0);
1158511731
break;
1158611732
case ZEND_AST_GROUP_USE:

Zend/zend_execute_API.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
#include "zend_observer.h"
4040
#include "zend_call_stack.h"
4141
#include "zend_frameless_function.h"
42+
#include "zend_namespaces.h"
4243
#ifdef HAVE_SYS_TIME_H
4344
#include <sys/time.h>
4445
#endif
@@ -148,6 +149,8 @@ void init_executor(void) /* {{{ */
148149

149150
EG(function_table) = CG(function_table);
150151
EG(class_table) = CG(class_table);
152+
EG(namespaces) = NULL;
153+
EG(global_namespace) = NULL;
151154

152155
EG(in_autoload) = NULL;
153156
EG(error_handling) = EH_NORMAL;
@@ -496,6 +499,8 @@ void shutdown_executor(void) /* {{{ */
496499
FREE_HASHTABLE(*EG(symtable_cache_ptr));
497500
}
498501

502+
zend_destroy_namespaces();
503+
499504
zend_hash_destroy(&EG(included_files));
500505

501506
zend_stack_destroy(&EG(user_error_handlers_error_reporting));

Zend/zend_globals.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ struct _zend_compiler_globals {
8585
zend_stack loop_var_stack;
8686

8787
zend_class_entry *active_class_entry;
88+
HashTable *nested_class_queue;
8889

8990
zend_string *compiled_filename;
9091

Zend/zend_namespaces.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ zend_class_entry *zend_resolve_namespace(zend_string *name) {
6666
if (EG(global_namespace) == NULL) {
6767
EG(global_namespace) = create_namespace(zend_empty_string);
6868
EG(global_namespace)->lexical_scope = NULL;
69+
ALLOC_HASHTABLE(EG(namespaces));
6970
zend_hash_init(EG(namespaces), 8, NULL, ZEND_CLASS_DTOR, 0);
7071
zend_hash_add_ptr(EG(namespaces), zend_empty_string, EG(global_namespace));
7172
}
@@ -93,3 +94,11 @@ zend_class_entry *zend_lookup_namespace(zend_string *name) {
9394

9495
return ns;
9596
}
97+
98+
void zend_destroy_namespaces(void) {
99+
zend_hash_destroy(EG(namespaces));
100+
FREE_HASHTABLE(EG(namespaces));
101+
EG(namespaces) = NULL;
102+
pefree(EG(global_namespace), 0);
103+
EG(global_namespace) = NULL;
104+
}

Zend/zend_namespaces.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,6 @@
2424

2525
ZEND_API zend_class_entry *zend_resolve_namespace(zend_string *name);
2626
ZEND_API zend_class_entry *zend_lookup_namespace(zend_string *name);
27+
ZEND_API void zend_destroy_namespaces(void);
2728

2829
#endif //ZEND_NAMESPACES_H

0 commit comments

Comments
 (0)