need name shadowing rules for `Self`
zygoloid opened this issue · comments
Given
class A {
impl i32 as AddWith(Self) {
// ... Self ...
}
}
... does Self
inside the impl
refer to A
, to i32
, or is it invalid to use it due to ambiguity? Same question applies for Self
on the right-hand side of the as
. Explorer treats Self
here as being A
-- an impl
only introduces a Self
if there's not already a Self
in scope.
The same question arises for nested classes:
class A {
class B {
// ... Self ...
}
}
I think I would expect that Self
is B
within class B
, rather than naming A
or being ambiguous. Especially given that a lookup for Self
within the body of B
should presumably behave the same way if B
is defined out of line:
class A {
class B;
}
class A.B {
// ... Self ...
}
... and it would be very surprising if Self
here didn't work.
Presumably interface
s can be nested within class
es, where the same question would arise:
class A {
interface B {
// ... Self ...
}
}
Is Self
now the type implementing the interface, or is it A
? I think in this case it must be the type implementing the interface, because there would otherwise be no way to name that type.
Some options to consider:
- A
class
,interface
, orimpl
declaration always introducesSelf
, as the class type or implementing type.Self
refers to the innermost such declaration. - A
class
orinterface
always introducesSelf
. Animpl
only introducesSelf
if there's noSelf
in scope. - An
impl
doesn't introduceSelf
ever.
Another option is that an impl
declaration doesn't introduce Self
, but Self
is introduced in the body of an impl
definition.
I am not excited about option 3, since I think Self
will shorten long type names in many circumstances, and no Self
would make it hard to copy from the interface
definition into the impl
body.
We have some examples in explorer's prelude that would be broken by Self
coming into scope at the {
rather than the as
, for example:
impl String as EqWith(Self) {
As the type here gets more complicated, we might feel more pressure to make something like this work. @chandlerc observed that we use .Self
for similar things in other contexts, so we could introduce the type on the left of the as
as .Self
on the right-hand side:
impl String as EqWith(.Self) { ... }
// Note that this already works -- `.Self` is in scope after `where`.
impl String as AddWith(String) where .Result = .Self { ... }
// And this already works.
let T:! EqWith(.Self) = String;
It might be a bit strange that we can use .Self
between as
and {
, but Self
within {
... }
, but I think the general rules here are:
- You can use
.Self
in contexts that expect a constraint, and it refers to the constrained type. - You can use
Self
within the braces of a declarative block to refer to the type whose API is described by that block.
One other thing we might want to consider: should (String as EqWith(.Self)).Op
be valid? Currently, .Self
is not in scope on the right-hand side of an as
operator in an expression, but it would be useful in a case like this for it to be valid, and that would be consistent with the treatment of impl ... as
suggested above.
FWIW, I like this last idea (including saying (String as EqWith(.Self)).Op
is a thing).
As for where all this would apply -- whenever we are constraining a type? That seems to cover the :!
bindings, where
clauses, impl T as
, and the as
operator here?
The only part I struggle with is the spelling itself of .Self
. But while I struggle with it in some ways, in most ways it is better than the alternatives I can come up with. And if we ever want to change the spelling, that seems better and easily done orthogonally at some point so hopefully there's no need to gate on it.
There is need for this. Look at your reference material for details.
Should this be a question for leads @zygoloid ? And do we have an answer?
Do we need a proposal?
Yes, I think we have enough analysis here that we can reasonably make this a leads question and answer it. I think the resolution we've converged on is:
Self
comes into scope at the{
of aninterface
,impl
,class
, ormixin
definition, and shadows any outerSelf
. (We could either say thatSelf
is a member name, or that it's added to the scope directly, depending on whether we wantT.Self
to work as a way to nameT
.).Self
comes into scope at anas
(in animpl
or in anas
expression),:!
, orwhere
, and names "the thing on the left" -- inimpl T as X
,T as X
, andT:! X
,.Self
inX
refers toT
, and inA where B
, the typeT:! A
constrained by thewhere
clause is.Self
in the constraints inB
.
Previously we've said that if there's more than one .Self
in scope, they must all agree, in some loose sense (they can have different facet types so long as they have the same value). I'm not sure whether we still want that, or how we'd implement it if so -- maybe a shadowing rule for .Self
would be more consistent.
I think we need a proposal to explore the details, but we can probably answer the broad questions as above.
Previously we've said that if there's more than one
.Self
in scope, they must all agree, in some loose sense (they can have different facet types so long as they have the same value). I'm not sure whether we still want that, or how we'd implement it if so -- maybe a shadowing rule for.Self
would be more consistent.
This is probably one of the details to work out in a proposal, but freely shadowing seems both logical and reasonable, but potentially confusing. Specifically, I'm imagining a case like: A where .T = Foo(.Self) and .U = Bar(.T as Baz(.Self))
-- we need a pretty simple rule for whether the second .Self
refers to A.T
or A
and to try to avoid confusion because of the nearness of the two usages if they differ.
Previously we've said that if there's more than one
.Self
in scope, they must all agree, in some loose sense (they can have different facet types so long as they have the same value). I'm not sure whether we still want that, or how we'd implement it if so -- maybe a shadowing rule for.Self
would be more consistent.This is probably one of the details to work out in a proposal, but freely shadowing seems both logical and reasonable, but potentially confusing. Specifically, I'm imagining a case like:
A where .T = Foo(.Self) and .U = Bar(.T as Baz(.Self))
-- we need a pretty simple rule for whether the second.Self
refers toA.T
orA
and to try to avoid confusion because of the nearness of the two usages if they differ.
Would it be reasonable to say that .Self
must "approximately agree" within an expression, but would otherwise shadow .Self
from an enclosing construct?
Previously we've said that if there's more than one
.Self
in scope, they must all agree, in some loose sense (they can have different facet types so long as they have the same value). I'm not sure whether we still want that, or how we'd implement it if so -- maybe a shadowing rule for.Self
would be more consistent.This is probably one of the details to work out in a proposal, but freely shadowing seems both logical and reasonable, but potentially confusing. Specifically, I'm imagining a case like:
A where .T = Foo(.Self) and .U = Bar(.T as Baz(.Self))
-- we need a pretty simple rule for whether the second.Self
refers toA.T
orA
and to try to avoid confusion because of the nearness of the two usages if they differ.Would it be reasonable to say that
.Self
must "approximately agree" within an expression, but would otherwise shadow.Self
from an enclosing construct?
Where do we get two .Self
s that might need to shadow but don't come from within an expression? I had thought my example was all a single type expression, and even a single where
clause "expression" such as it is?
Oh, are you specifically suggesting that within a particular branch of a where
clause the .Self
s agree? That would suggest the first .Self
in my example refers te A
and the second to .T
?
I was just mistaken.