On 08/11/2025 20:08, Rob Landers wrote:
The moment we allow "namespace prefixes", we introduce questions that
need to be fleshed out separately and with care. Do we treat
"Acme\AuthLib\Session" and "Acme\AuthLib\Services" as related? Even if
they come from different vendors and are unrelated in any way? Are
namespaces hierarchical or just flat strings that happen to contain
separators?
While I appreciate the desire to keep things simple, I don't think we
can avoid asking those questions, we can only propose answers. In your
current RFC, the answers are:
- No two namespaces are related, even if their names imply a hierarchy.
- Two "internal" classes in the same namespace can see each other even
if they are written by different vendors.
Today namespaces are a name resolution mechanism, not a semantic
hierarchy. Matching by prefix would start treating them as a package
system, and I explicitly am trying to avoid that in this RFC.
I don't think that's true. The language already recognises the namespace
separator, and that if you are in namespace "Acme\AuthLib", an
unqualified reference to "Services\SessionManager" refers
to "Acme\AuthLib\Services\SessionManager". That doesn't require any
concept of "package", but it is clearly based on the conception of
namespaces as a hierarchy.
By restricting the rule to exact namespace equality, the feature is
straightforward to explain and to understand. It also prevents
"accidental" access because two unrelated namespaces happen to share a
prefix.
My concern is that by restricting it so much, we would prevent most of
the real-world use cases for it, since the reality is that people use
much more complex namespace hierarchies.
If you have a concrete, unambiguous rule for prefix-based access that
wouldn’t cause surprises or make assumptions about how people organise
their codebases, I’d be happy to discuss it. Right now, every version
I’ve explored would reopen the "module"/"package" debate from this summer.
I mentioned, briefly, two possibilities:
I think we need a keyword or attribute which takes as a parameter
either the namespace prefix, or the number of levels to match
To spell those out, the prefix version could look like this:
namespace Acme\AuthLib\Somewhere\Deep\In\Package;
// ...
#[NamespacePrivate('Acme\AuthLib', includeChildren: true)]
A number of levels version could look like this:
namespace Acme\AuthLib\Somewhere\Deep\In\Package;
// ...
#[NamespacePrivate(minLevels: 2, maxLevels: null)]
There's all sorts of variations on how those arguments could be
presented, keywords vs attributes, etc; but the key point is the
language is not defining where the boundary is, it is requiring the user
to do so.
The prefix-based approach could be expanded into an allow-list that
didn't require any relationship to the current namespace at all:
namespace Acme\AuthLib\Somewhere\Deep\In\Package;
// ...
#[AllowNamespace('Acme\AuthLib\*')]
#[AllowNamespace('Zeppo\FrameworkCore\*')]
Or even an allow-deny rule system:
#[AllowNamespace('Acme\AuthLib\*')]
#[DenyNamespace('Acme\AuthLib\Plugins\*')]
At that point, it's admittedly rather complex, but it makes *even fewer*
assumptions about what constitutes a "package".
--
Rowan Tommins
[IMSoP]