Skip to content

RFC: Static class #14861

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

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion Zend/Optimizer/escape_analysis.c
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ static bool is_allocation_def(zend_op_array *op_array, zend_ssa *ssa, int def, i
script, op_array, opline);
uint32_t forbidden_flags =
/* These flags will always cause an exception */
ZEND_ACC_IMPLICIT_ABSTRACT_CLASS | ZEND_ACC_EXPLICIT_ABSTRACT_CLASS
ZEND_ACC_STATIC | ZEND_ACC_IMPLICIT_ABSTRACT_CLASS | ZEND_ACC_EXPLICIT_ABSTRACT_CLASS
| ZEND_ACC_INTERFACE | ZEND_ACC_TRAIT;
if (ce
&& !ce->parent
Expand Down
2 changes: 1 addition & 1 deletion Zend/tests/readonly_classes/readonly_enum.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ readonly enum Foo

?>
--EXPECTF--
Parse error: syntax error, unexpected token "enum", expecting "abstract" or "final" or "readonly" or "class" in %s on line %d
Parse error: syntax error, unexpected token "enum" in %s on line %d
2 changes: 1 addition & 1 deletion Zend/tests/readonly_classes/readonly_interface.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ readonly interface Foo

?>
--EXPECTF--
Parse error: syntax error, unexpected token "interface", expecting "abstract" or "final" or "readonly" or "class" in %s on line %d
Parse error: syntax error, unexpected token "interface" in %s on line %d
2 changes: 1 addition & 1 deletion Zend/tests/readonly_classes/readonly_trait.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ readonly trait Foo

?>
--EXPECTF--
Parse error: syntax error, unexpected token "trait", expecting "abstract" or "final" or "readonly" or "class" in %s on line %d
Parse error: syntax error, unexpected token "trait" in %s on line %d
11 changes: 11 additions & 0 deletions Zend/tests/static_class/inherit_non-static_subclass.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
--TEST--
Tests that a non-static class cannot inherit from a static class
--FILE--
<?php

static class C {}

class C2 extends C {}
?>
--EXPECTF--
Fatal error: Non-static class C2 cannot extend static class C in %s on line %d
11 changes: 11 additions & 0 deletions Zend/tests/static_class/inherit_non-static_superclass.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
--TEST--
Tests that a static class cannot inherit from a non-static class
--FILE--
<?php

class C {}

static class C2 extends C {}
?>
--EXPECTF--
Fatal error: Static class C2 cannot extend non-static class C in %s on line %d
17 changes: 17 additions & 0 deletions Zend/tests/static_class/inherit_static_class.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
--TEST--
Tests that a static class can inherit from another static class
--FILE--
<?php

static class C {
static function F() {
echo 'OK';
}
}

static class C2 extends C {}

C2::F();
?>
--EXPECT--
OK
21 changes: 21 additions & 0 deletions Zend/tests/static_class/inherit_trait.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
--TEST--
Tests that a static class can inherit static members from a trait
--FILE--
<?php

trait T {
static $P = 'OK';

static function F() {
echo self::$P;
}
}

static class C {
use T;
}

C::F();
?>
--EXPECT--
OK
15 changes: 15 additions & 0 deletions Zend/tests/static_class/inherit_trait_instance_method.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
--TEST--
Tests that a static class cannot inherit an instance method from a trait
--FILE--
<?php

trait T {
function F() {}
}

static class C {
use T;
}
?>
--EXPECTF--
Fatal error: Static class C cannot use trait with a non-static method T::F in %s on line %d
15 changes: 15 additions & 0 deletions Zend/tests/static_class/inherit_trait_instance_property.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
--TEST--
Tests that a static class cannot inherit an instance property from a trait
--FILE--
<?php

trait T {
var $V;
}

static class C {
use T;
}
?>
--EXPECTF--
Fatal error: Static class C cannot use trait with a non-static property T::$V in %s on line %d
12 changes: 12 additions & 0 deletions Zend/tests/static_class/new_static_class.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
--TEST--
Tests that a static class cannot be instantiated
--FILE--
<?php

static class C {}

new C;
?>
--EXPECTF--
Fatal error: Uncaught Error: Cannot instantiate static class C in %s:%d
Stack trace:%a
12 changes: 12 additions & 0 deletions Zend/tests/static_class/new_static_class_via_reflection.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
--TEST--
Tests that a static class cannot be instantiated via reflection
--FILE--
<?php

static class C {}

new ReflectionClass('C')->newInstance();
?>
--EXPECTF--
Fatal error: Uncaught Error: Cannot instantiate static class C in %s:%d
Stack trace:%a
19 changes: 19 additions & 0 deletions Zend/tests/static_class/static_abstract_class.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
--TEST--
Tests that a static class can be marked abstract
--FILE--
<?php

abstract static class C {
abstract static function F();
}

static class C2 extends C {
static function F() {
echo 'OK';
}
}

C2::F();
?>
--EXPECT--
OK
9 changes: 9 additions & 0 deletions Zend/tests/static_class/static_anonymous_class.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
--TEST--
Tests that an anonymous class cannot be marked static
--FILE--
<?php

new static class C {}
?>
--EXPECTF--
Fatal error: Cannot use the static modifier on an anonymous class in %s on line %d
17 changes: 17 additions & 0 deletions Zend/tests/static_class/static_class.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
--TEST--
Tests that a class can be marked static and functions as a static class
--FILE--
<?php

static class C {
static $P = 'OK';

static function F() {
echo self::$P;
}
}

C::F();
?>
--EXPECT--
OK
13 changes: 13 additions & 0 deletions Zend/tests/static_class/static_class_ast_export.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
--TEST--
Tests that the AST export of a static class includes the static modifier
--FILE--
<?php

assert(false && function () {
static class C {}
});
?>
--EXPECTF--
%a
%wstatic class C {%w}
%a
15 changes: 15 additions & 0 deletions Zend/tests/static_class/static_class_call_static.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
--TEST--
Tests that a static class allows __callStatic() magic method
--FILE--
<?php

static class C {
static function __callStatic(string $name, array $arguments) {
echo $name;
}
}

C::F();
?>
--EXPECT--
F
11 changes: 11 additions & 0 deletions Zend/tests/static_class/static_class_instance_method.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
--TEST--
Tests that a static class cannot contain an instance method
--FILE--
<?php

static class C {
function F() {}
}
?>
--EXPECTF--
Fatal error: Class method C::F() must be declared static in a static class in %s on line %d
11 changes: 11 additions & 0 deletions Zend/tests/static_class/static_final_class.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
--TEST--
Tests that a static class can be marked final
--FILE--
<?php

final static class C {}

static class C2 extends C {}
?>
--EXPECTF--
Fatal error: Class C2 cannot extend final class C in %s on line %d
9 changes: 9 additions & 0 deletions Zend/tests/static_class/static_interface.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
--TEST--
Tests that an interface cannot be declared static
--FILE--
<?php

static interface I {}
?>
--EXPECTF--
Parse error: syntax error, unexpected token "interface" in %s on line %d
9 changes: 9 additions & 0 deletions Zend/tests/static_class/static_readonly_class.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
--TEST--
Tests that a static class cannot be marked readonly
--FILE--
<?php

static readonly class C {}
?>
--EXPECTF--
Fatal error: Cannot use the static modifier on a readonly class in %s on line %d
9 changes: 9 additions & 0 deletions Zend/tests/static_class/static_static_class.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
--TEST--
Tests that a static class cannot be so marked more than once
--FILE--
<?php

static static class C {}
?>
--EXPECTF--
Fatal error: Multiple static modifiers are not allowed in %s on line %d
9 changes: 9 additions & 0 deletions Zend/tests/static_class/static_trait.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
--TEST--
Tests that a trait cannot be declared static
--FILE--
<?php

static trait T {}
?>
--EXPECTF--
Parse error: syntax error, unexpected token "trait" in %s on line %d
12 changes: 12 additions & 0 deletions Zend/tests/static_class/unserialize_static_class.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
--TEST--
Tests that a static class cannot be created via unserialize()
--FILE--
<?php

static class C {}

unserialize('O:1:"C":0:{}');
?>
--EXPECTF--
Fatal error: Uncaught Error: Cannot instantiate static class C in %s:%d
Stack trace:%a
4 changes: 3 additions & 1 deletion Zend/zend_API.c
Original file line number Diff line number Diff line change
Expand Up @@ -1795,13 +1795,15 @@ ZEND_API void object_properties_load(zend_object *object, HashTable *properties)
* calling zend_merge_properties(). */
static zend_always_inline zend_result _object_and_properties_init(zval *arg, zend_class_entry *class_type, HashTable *properties) /* {{{ */
{
if (UNEXPECTED(class_type->ce_flags & (ZEND_ACC_INTERFACE|ZEND_ACC_TRAIT|ZEND_ACC_IMPLICIT_ABSTRACT_CLASS|ZEND_ACC_EXPLICIT_ABSTRACT_CLASS|ZEND_ACC_ENUM))) {
if (UNEXPECTED(class_type->ce_flags & (ZEND_ACC_STATIC|ZEND_ACC_INTERFACE|ZEND_ACC_TRAIT|ZEND_ACC_IMPLICIT_ABSTRACT_CLASS|ZEND_ACC_EXPLICIT_ABSTRACT_CLASS|ZEND_ACC_ENUM))) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is repeated often enough to where it probably makes sense to extract it into a new macro. E.g. ZEND_ACC_UNINSTANTIABLE_FLAGS.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you perhaps agree this is best left for a separate refactoring PR?

if (class_type->ce_flags & ZEND_ACC_INTERFACE) {
zend_throw_error(NULL, "Cannot instantiate interface %s", ZSTR_VAL(class_type->name));
} else if (class_type->ce_flags & ZEND_ACC_TRAIT) {
zend_throw_error(NULL, "Cannot instantiate trait %s", ZSTR_VAL(class_type->name));
} else if (class_type->ce_flags & ZEND_ACC_ENUM) {
zend_throw_error(NULL, "Cannot instantiate enum %s", ZSTR_VAL(class_type->name));
} else if (class_type->ce_flags & ZEND_ACC_STATIC) {
zend_throw_error(NULL, "Cannot instantiate static class %s", ZSTR_VAL(class_type->name));
} else {
zend_throw_error(NULL, "Cannot instantiate abstract class %s", ZSTR_VAL(class_type->name));
}
Expand Down
3 changes: 3 additions & 0 deletions Zend/zend_ast.c
Original file line number Diff line number Diff line change
Expand Up @@ -1837,6 +1837,9 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio
if (decl->flags & ZEND_ACC_EXPLICIT_ABSTRACT_CLASS) {
smart_str_appends(str, "abstract ");
}
if (decl->flags & ZEND_ACC_STATIC) {
smart_str_appends(str, "static ");
}
if (decl->flags & ZEND_ACC_FINAL) {
smart_str_appends(str, "final ");
}
Expand Down
19 changes: 19 additions & 0 deletions Zend/zend_compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -944,6 +944,16 @@ uint32_t zend_add_class_modifier(uint32_t flags, uint32_t new_flag) /* {{{ */
"Cannot use the final modifier on an abstract class", 0);
return 0;
}
if ((new_flags & ZEND_ACC_STATIC) && (new_flags & ZEND_ACC_READONLY_CLASS)) {
zend_throw_exception(zend_ce_compile_error, "Cannot use the static modifier on a readonly class", 0);
return 0;
}
if ((flags & ZEND_ACC_STATIC) && (new_flag & ZEND_ACC_STATIC)) {
zend_throw_exception(zend_ce_compile_error,
"Multiple static modifiers are not allowed", 0);
return 0;
}

return new_flags;
}
/* }}} */
Expand All @@ -960,6 +970,10 @@ uint32_t zend_add_anonymous_class_modifier(uint32_t flags, uint32_t new_flag)
zend_throw_exception(zend_ce_compile_error, "Cannot use the final modifier on an anonymous class", 0);
return 0;
}
if (new_flag & ZEND_ACC_STATIC) {
zend_throw_exception(zend_ce_compile_error, "Cannot use the static modifier on an anonymous class", 0);
return 0;
}
if ((flags & ZEND_ACC_READONLY_CLASS) && (new_flag & ZEND_ACC_READONLY_CLASS)) {
zend_throw_exception(zend_ce_compile_error, "Multiple readonly modifiers are not allowed", 0);
return 0;
Expand Down Expand Up @@ -7838,6 +7852,11 @@ static zend_string *zend_begin_method_decl(zend_op_array *op_array, zend_string
zend_error(E_COMPILE_WARNING, "Private methods cannot be final as they are never overridden by other classes");
}

if ((ce->ce_flags & ZEND_ACC_STATIC) && !(fn_flags & ZEND_ACC_STATIC)) {
zend_error(E_COMPILE_ERROR, "Class method %s::%s() must be declared static in a static class",
ZSTR_VAL(ce->name), ZSTR_VAL(name));
}

if (in_interface) {
if (!(fn_flags & ZEND_ACC_PUBLIC)) {
zend_error_noreturn(E_COMPILE_ERROR, "Access type for interface method "
Expand Down
6 changes: 3 additions & 3 deletions Zend/zend_compile.h
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ typedef struct _zend_oparray_context {
#define ZEND_ACC_CHANGED (1 << 3) /* | X | X | */
/* | | | */
/* Static method or property | | | */
#define ZEND_ACC_STATIC (1 << 4) /* | X | X | */
#define ZEND_ACC_STATIC (1 << 4) /* X | X | X | */
/* | | | */
/* Promoted property / parameter | | | */
#define ZEND_ACC_PROMOTED (1 << 5) /* | | X | X */
Expand Down Expand Up @@ -251,7 +251,7 @@ typedef struct _zend_oparray_context {
/* or IS_CONSTANT_VISITED_MARK | | | */
#define ZEND_CLASS_CONST_IS_CASE (1 << 6) /* | | | X */
/* | | | */
/* Class Flags (unused: 30,31) | | | */
/* Class Flags (unused: 31) | | | */
/* =========== | | | */
/* | | | */
/* Special class types | | | */
Expand All @@ -265,7 +265,7 @@ typedef struct _zend_oparray_context {
/* | | | */
/* Class is abstract, since it is set by any | | | */
/* abstract method | | | */
#define ZEND_ACC_IMPLICIT_ABSTRACT_CLASS (1 << 4) /* X | | | */
#define ZEND_ACC_IMPLICIT_ABSTRACT_CLASS (1 << 30) /* X | | | */
/* | | | */
/* Class has magic methods __get/__set/__unset/ | | | */
/* __isset that use guards | | | */
Expand Down
Loading
Loading