-
Notifications
You must be signed in to change notification settings - Fork 205
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
Allow variables named this
#328
Comments
This only allows {
var this = StringBuffer();
add("42");
addAll(something);
...
return toString();
} It seems likely that a user will want to locally use a different |
@lrhn wrote:
It does allow for I still expect that we'd want to constrain this mechanism somewhat, because it will be confusing if it is used too aggressively, but it's very easy to point out some cases and make them an error. |
I'd be very wary at allowing I think I'd go for only allowing it for local variables.
So, I'd make One other worry: If you write an identifier |
@lrhn wrote:
We do have a kind of a use case: It serves very nicely as a model for why extension methods can access the syntactic receiver using implicit member access and via an explicit In general, it would enable all instance methods of a given class to behave as if the class were forwarding to a given instance variable. A similar property would apply for static variables, in which case static methods would also participate in the forwarding. So I agree that it is a mechanism that should be used judiciously, but then it can actually be quite powerful.
We might very well say that |
A more significant problem with making If you write |
@lrhn wrote:
The specification says that (That location used to say For the current proposal we'd need to add something like a notion of a "targeted Then we'd execute instance methods of a class For ordinary method invocations we would then have an additional bullet (here) specifying that This means it is always possible to access the current instance of an enclosing class in an instance method; so When there is no declaration named When there is a declaration named We would need to change the last bullet such that it changes Superinvocations would be defined in terms of
If we consider an explicit reference to class A {
int this;
}
class B extends A {
bool foo() => this.isEven; // Rewritten to `C.this.this.isEven`
} Conversely, consider an unqualified invocation class A {
int this;
}
class B extends A {
foo() {
print(isEven); // Error.
}
} In this situation we transform If we want to have the effect where that class A {
int this;
}
class B extends A {
int get this; // Abstract getter puts a declared `this` in scope.
foo() {
print(isEven); // Desugars to `this.isEven`, OK statically and dynamically.
}
} For the dynamic semantics, the term So I don't think we have an infinite regress problem. Finally, it could be debated, but I would expect the ability to look up members named If we consider a field named |
The use of class C {
static final B this = ...;
int foo(D this) =>
this.foo() + C.this.foo();
} Here I can live with not allowing static/top-level |
@lrhn wrote:
class C {
static final B this = ...;
int foo(D this) =>
this.foo() + C.this.foo();
}
If we do insist on allowing Conceptually, we could think of this as a certain kind of privacy: When we use a variable or getter named class C {
static final B this = ...;
static B get forwardee => this; // Allow remote access.
int foo(D this) =>
this.foo() + // Invoke `D.foo` on the argument.
C.forwardee.foo(); // Invoke `B.foo()` on the static variable.
} |
In response to #266, this issue proposes user-declared implicit member access, which is simply the feature which adds the ability for a variable (which could be a formal parameter, or any kind of variable) or getter to have the name
this
.[Edit, May 3rd 2019: Clarified some errors, added an error when
this
has the typedynamic
. May 7th 2019: Introducing a targetedthis
and more detailed lookup rules; added type-safe builder example.]In Dart, the lexical scope is searched first, and any declaration found there is used. For instance,
m(42)
will call the library function namedm
in the body ofm2
, not the instance methodm
which is inherited fromA
:In the case where the lexical scope does not contain a declaration of the requested name,
this.
is prepended, and the resulting expression is used during subsequent static analysis. (So we check the lexical scopes, thenthis
, but we may still end up with a compile-time error because the requested name is not found in any of those two ways).The feature proposed here is to allow declarations of variables and getters named
this
. The existing scope rules are used to obtain the effect thatthis
can be an implicit receiver: The enclosing lexical scopes will still be searched first, and member accesses withthis
as an implicit receiver will be taken into account if nothing was found in the lexical scopes, and we still get a compile-time error if none of those two approaches yield a successful lookup.The rules proposed here specify that, in the body of an instance method, an instance member invocation is chosen whenever an instance member of the given name exists in the interface of the enclosing class (not just when such a member is declared in the lexical scope). This may seem like a breaking change (because it mentions the interface of the class, and the current rules only talk about the lexical scope), but it is in fact backward compatible because the existing rules unconditionally prescribed the addition of
this
as the last catch-all case.The additional expressive power added by this feature is only the ability to give the name
this
to a larger set of objects, thus allowing for implicit member accesses to them.Note that multiple declarations of
this
in different lexical scopes can shadow each other, just like other declarations, such that the innermost declaration wins:This mechanism is likely to be very convenient in a number of situations.
In particular, it is commonly expected that implicit access to an object which is not the "current instance" of an enclosing class is available in extension methods (such as #41 and #177); that can be achieved simply by making the syntactic receiver of the extension method invocation be a parameter named
this
in the function which is the desugared version of the extension method. This means that we can use this general mechanism, and we don't need to invent a whole set of special rules to explain why it is possible to usethis
to access the syntactic receiver of an extension method invocation.In general, the use of a variable or getter named
this
allows for concise access to the instance members of that object.On the other hand, excessive usage of declarations named
this
will make it harder to read the source code. In the specification below we will leave it open how to constrain this mechanism, but it would of course be very easy to single out specific usages and make them a compile-time error (e.g., it could be a compile-time error for a top-level variable to have the namethis
).If we prefer to take a more cautious path, we could make the choice to only allow declarations named
this
in a very small number of specific situations. In all other situations it would just be a compile-time error for a declaration to have the namethis
.It would then presumably be quite easy (in terms of the specification as well as the implementation) to allow additional kinds of declarations to have the name
this
, whenever we have determined that this generalization is valuable in practice.Syntax
The grammar is adjusted as follows in order to support user-declared implicit member access:
Note that it is a syntax error to use the name
this
for a type variable, but other variables can have the namethis
.Static Analysis
It is a compile-time error for a variable or getter with the name
this
to have the typedynamic
.This is true both in the case where the variable has a type annotation, and in the case where the type of the variable is inferred.
It is a compile-time error for a
<targetedThisExpression>
of the formC.this
to occur, unless it occurs in an instance method of a class namedC
. A<targetedThisExpression>
is considered to be an identifier whose static type is the enclosing class, passing any declared type parameters as actual type arguments. (For instance,C.this
has typeC<X>
ifC
is declared asclass C<X> {...}
.)The static type of a
<thisExpression>
e is the type annotation of the declaration namedthis
if any such declaration is in scope; otherwise, the static type of e is the enclosing class, when e occurs in an instance method; otherwise, e is a compile-time error.In the rules for unqualified invocations, a new 5th bullet point is added (here), specifying that
and the last bullet is adjusted to say
Similar adjustments are made for identifier references here.
For instance, if an expression
e
of the formm(42)
occurs in the body of an instance method of a classC
then, if there is no declaration ofm
in the enclosing lexical scopes,e
is desugared tothis.m(42)
, and subsequent processing is based on the desugared expression. In particular, the static analysis ofe
will check that there is a member namedm
in the static interface of the type ofthis
, and that42
is an appropriate actual argument list for an invocation ofm
. Similarly, the dynamic semantics ofe
follows from the result of desugaring.Dynamic Semantics
When an ordinary method invocation takes place and it invokes an instance method
m
in a classC
with receivero
, the targeted this-expressionC.this
is bound too
; if no declaration namedthis
is in scope,this
is bound too
as well.If a declaration named
this
is in scope then no special bindings forthis
are provided, and the corresponding declared entity is accessed using the ordinary rules for access to a declared entity.Discussion
Kotlin supports the notion of a function literal with receiver and function type with receiver. An example is the following:
This specifies that the receiver type is
Int
, and it allows the call to pass an instance of typeInt
by providing it as the syntactic receiver (such as4
in4.sum(2)
), and to access that instance in the body of the function usingthis
.The Kotlin mechanism differs from the proposal of this issue by being more constrained: It bundles together two different mechanisms, and they can't be used separately. The first one is that a certain function argument is passed as a receiver (that is, that the call is of the form
r.f(...)
, butr
will be passed tof
as the first actual argument). The second mechanism is that the "receiver argument" is accessed in the body of the function using the namethis
, and it also allows for implicit accesses (such thatm()
in the body can denote the instance method invocationthis.m()
).The feature proposed in this issue, user-declared implicit member access, is only concerned with the scoping and the implicit member access, that is, the second mechanism mentioned above.
Other mechanisms (like #41, #42, #177, #309) are used to enable the first mechanism, and they in turn rely on this feature. This allows us to specify extension methods, extension types and so on in terms of a common underlying mechanism which allows
this
to take on the appropriate meaning in each case, as opposed to an approach where we would specify a new set of ad-hoc rules for how to understandthis
in the context of each of those constructs.A major use case for Kotlin's function types and literals with receiver is type-safe builders. Here is an example from here:
The point is that the construction of an instance of
HTML
is done in the functionhtml
, and the{...}
construct at the end is an argument tohtml
which is a function literal, and the body of that function literal gets to use methods on thatHTML
implicitly, because it is a function literal with receiver.If we choose to give the implicit parameter the name
this
then we could do something similar in Dart:It's a bit more noisy because of the parentheses and the semicolons, but we do get the basic structure of a Kotlin style type-safe build.
Note that Dart gives more information locally:
In Kotlin, you'd need to look up the type of the parameter of
html
(possibly in some other file) in order to understand that we may rely on an implicit receiver—for instance,body()
calls a method on the argument. In Dart, we immediately know thatbody()
can be a method call on the argument, because (if we choose the namethis
for the parameter) that's how abbreviated function literals work.The text was updated successfully, but these errors were encountered: