-
Notifications
You must be signed in to change notification settings - Fork 213
Make it safer to "enter" a (plain) view or an extension type #1665
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
Comments
Making the view type Reifying the closed view type was my first proposal, but we're gravitating towards a model which is consistently non-reified, as you might well expect from a language mechanism aimed at zero cost. The main difficulty with the reified design is that if the view type is reified as a type argument (and, most likely, as a type literal) as a value distinct from the on-type, then it seems reasonable (to me) that the associated dynamic checks should be specific to the view as well. For example: closed view IdNumber on int {}
void main() {
var ids = [IdNumber(24799)];
List<Object?> xs = ids;
xs.add(-1); // Is it good enough that we just check `-1 is int`?
} If a So the core consideration here is the following: If you can't ensure at run time that a |
View types will definitely be allowed as type arguments in a number of cases. Consider an open view. For this kind of view, the view type and the corresponding on-type are mutual subtypes. It is considered safe to switch between the on-type and the view type at any time (if that's not true then the declaration should not use For the plain views (where the declaration just starts with With Similarly, I suspect that it's a good idea to keep views strictly as a compile-time mechanism (in particular, even closed view types will be reified at run time as the corresponding on-type). This means that we can use an explicit cast to break the abstraction. If you want to protect the abstraction then you can make the choice to allocate a wrapper object rather than using a static mechanism, that is, you can use In summary, I tend to think that we shouldn't make it an error to use a view type as a type argument, for somewhat different reasons in each case. |
It wouldn't be hard, technically, to provide a separate variant of view types that are reified as type arguments. But you're probably right that it could be a hard sell ("we have plenty of view constructs already, why do you need yet another kind?"). But fat pointers is a whole other story, that's about a construct which has a run-time representation associated with the individual underlying object. A fat pointer is basically a compact implementation of a special kind of wrapper object (the wrapper has methods and access to the wrapped object, but no other state (and no mutable state), so we can use the wrapped object as the identity, and hence we don't need to worry about identity confusion). That's a very interesting topic as well, but quite different from views and zero-cost abstraction! |
I do believe this safer explicit conversion mechanism is important and has valuable uses. Consider for example how eons ago GWT reduced XSS vulnerabilities. They disallowed the equivalent of a
|
Changed labels to reflect the fact that the proposals in this issue will not be part of the upcoming extension types feature, but they could be considered later. |
This issue proposes a rather mild increase in the protection of views and extension types: We can require that a constructor is called in order to turn an expression of the on-type into an expression of the view/extension type, and still enable higher order cases (such as lists and functions) to allow assignments without a cast.
This proposal about views and this proposal about extension types allow us to make the on-type a subtype of the view or extension type (I'll mention views here, but
view
could be replaced byextension type
everywhere):This ensures that different views on the same kind of object are not mutually assignable, so we get an error if we assign
id
topn
. This is quite useful in the situation where we want to use a given representation (perhaps a very lightweight one likeint
) in several different ways that may need to be kept separate according to the application logic.However, this subtype based approach provides no checks on the introduction of new values of the given view type, we're always allowed to assign a given
int
to any of those...Number
view types:There is no compile-time error in
main
above, and no run-time error, it is a logical error because those two numbers were intended to be passed in the opposite order. So the program has a bug even though it might not crash.In order to improve on the correctness support at the point where instances typed as a view type are obtained, we could add a simple rule to the above mentioned proposals:
Assignability is extended by an extra check: A type T is assignable to a type S if
dynamic
, orA conversion constructor is a view constructor with the same name as the view that takes a single, mandatory, positional argument whose type is the on-type of the view.
This means that if we declare a conversion constructor in a view then it is required that we call this constructor in the case where we wish to use an expression whose type is the on-type where the view type is expected.
For example:
Note that we can of course use normal abstraction on top of the conversion constructor, if we wish to make things look a bit differently:
The reason why it could be useful to change assignability and keep the subtype relationship from the on-type to the view type is that this allows us to transparently enable adoption of the view on composite entities. For example, we can create a
List<int>
in some context (whereIdNumber
is not available), and then we can work with that list (there's no need to copy it) as aList<IdNumber>
:PS: In some situations we might wish to take away the assignability in the higher-order cases, too. In order to achieve that we could use something like a closed view (mentioned in the view proposal), so we can make this distinction a choice that developers can make. Example:
The cast
as List<IdNumber>
will succeed at run time (assuming the current proposal), because every view type is reified as the corresponding on-type at run time. However, it is statically unsafe in the sense that we could as well have madexs
aSet<int>
(or aString
, or anything at all), and in that casexs as List<IdNumber>
would throw at run time even though there is no compile-time error. This is a major reason why we might want to offer the mild protection described in this issue.The text was updated successfully, but these errors were encountered: