-
Notifications
You must be signed in to change notification settings - Fork 7.8k
RFC: Nested Classes #18207
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
base: master
Are you sure you want to change the base?
RFC: Nested Classes #18207
Conversation
The idea here is to create something that can be used for other things essentially making this quite flexible. required scope: when defined, the class can only be used in the required scope, or its descendents when absolute is false lexical scope: the scope the class is defined in -- either a namespace or another class.
Now we modify the grammar to allow specifying visibility on a class.
This adds a new EG to support the addition of using namespaces as a class's lexical scope
Now we can define nested classes inside other classes
The idea is good, but since support would be added to indicate the visibility of the class, it would not be better to add such encapsulation within the Namespace?, something like what Java does with the packages |
I'm not sure what you mean. If you mean, can this be used to create packages? Yes, it can; you need only a few changes to support that. |
for example namespace A {
public class PublicClass {
public function __construct() {
new PrivateClass; // works
}
}
private class PrivateClass {}
}
namespace {
$publicClass = new A\PublicClass();
$privateClass = new A\PrivateClass(); // error
} |
That is relatively simple to implement from here. This implementation leans on the fact that the parser thinks it is about to parse a property to expect public/private/protected, and the fact that properties reuse final/static/readonly/etc. It then has to translate the property modifiers into class modifiers during compilation... but there is currently only one slot left in ZEND_ACC for classes -- this is why I chose not to use a special ZEND_ACC slot for this feature. To implement packages, like you mention, we would need to take a ZEND_ACC slot, most likely; because this way of doing it won't work for top-level classes (IIRC). Anyway, that's most likely an implementation detail to cross when we get there -- and the actual implementation may just use a ZEND_ACC slot, especially if packages and this were to pass. I intend to follow this up with packages/modules. |
The only thing I want is to type PackageClass in the editor and LSP-intelephense doesn't burn me with 100 classes with the same name but different namespaces, some ones are internals from dependencies, if they were private, the autocomplete only shows me the public classes of those dependencies |
This is a complete rewrite of the previous PR: #18069
There are a couple things going on here and can be broken up into two parts:
zend_class_entry
types. This is used by nested classes for fast scope resolution and can be further used by a module implementation. These namespaces cannot be instantiated but are simply used for scope resolution only, as every class now has a "lexical scope" that is either a namespace or an outer class.\
as a scope operator.These two work together to allow nested types to be resolved correctly at compile time:
This implementation works through the use of three members on the
zend_class_entry
struct:required_scope
: for requiring that the class is used within a specific scope.required_scope_absolute
: when set tofalse
,required_scope
checks can useinstanceof
, otherwise the required scope must match exactly; thusfalse
pertains toprotected
andtrue
pertains toprivate
.lexical_scope
: the class or namespace the class belongs to; used for visibility.There are probably a number of things that can be improved in this PR, so feel free to suggest how this can be improved.
I'd like to improve how namespaces are managed, for example (this PR inspired #18189 and will likely benefit from what I learn there).
Other attempts
A different approach would involve using name mangling instead; however, this created issues: visibility and required scopes are subtly different from each other. This isn't as much an issue with things like properties and methods, because they are always the same rules. Nested types may be visible, but not be allowed to be used outside their scope:
Outer::foo()
can "see"Outer\Middle\Inner
, but the inner types cannot be exposed outside the outer class. These rules can possibly be implemented via name mangling, but some benchmarks showed that even a deeply recursive tree of pointer equality is often faster than even simple string operations.