wing
wing copied to clipboard
Dedicated casting syntax
Feature Spec
In Wing 0.x, the unsafeCast
builtin function has been replaced with a dedicated syntax.
Use Cases
Today unsafeCast
doesn't let you specify the type you are casting to because it is just a blanket function that lets you to treat a value as type anything
. A casting syntax can address this. Most other modern languages also support casting through a dedicated syntax.
Implementation Notes
Random syntax ideas:
-
x as cloud.Bucket
(note: conflicts with existing "as" syntax for preflight class instantiation https://www.winglang.io/docs/language-reference#33-preflight-classes) -
x -> cloud.Bucket
-
x asType cloud.Bucket
-
@cloud.Bucket x
-
<cloud.Bucket>x
-
(cloud.Bucket)x
-
[cloud.Bucket]x
-
cast<cloud.Bucket>(x)
Component
Language Design, Compiler
Community Notes
- Please vote by adding a 👍 reaction to the issue to help us prioritize.
- If you are interested to work on this issue, please leave a comment.
- If this issue is labeled needs-discussion, it means the spec has not been finalized yet. Please reach out on the #dev channel in the Wing Slack.
This is already done no?
@eladb We added unsafeCast
function, but I'm not sure it's the right DX since it leaks the any
type without a strong reason. A dedicated syntax like (cloud.Bucket)x
would require you to specify what type you are casting to. I've suggested some ideas in the issue.
I am not sure I completely understand... Isn't unsafeCast()
inferring the target type based on what we are assigning to?
let x: Foo = unsafeCast(bar); // cast to `Foo`
let y: Zing = unsafeCast(bar); // cast to `Zing`
@eladb Sorry my explanation wasn't clear. Here's an example of what I mean:
let b = new cloud.Bucket();
let z = unsafeCast(b);
In this example, z
is now type any
(that's what it shows when you hover over the variable in VS Code). This means it can be passed to any functions, and any properties or methods that don't exist on it can accessed. To date the team has put in a lot of effort into keeping Wing type-safe per the language's goals, including avoiding exposing any
types.
Do you have use cases in mind where you need the capability to cast something as any
?
I see. Can we forbid this particular situation and require that unsafeCast
will always be used in the context of an explicitly defined type? I understand there's some "reverse type inference" thing that needs to happen but maybe if it's not too hard to implement, we don't need the dedicated syntax.
Can we forbid this particular situation and require that unsafeCast will always be used in the context of an explicitly defined type? I understand there's some "reverse type inference" thing that needs to happen...
I'm not sure if allowing the target type to be inferred from context feels right - here's an example of what I'm worried about:
Let's say a user wants to cast an IConstruct
into a cloud.Bucket
so they can pass it to a class constructor from a third party library. A user has written this code so far:
bring "file-copier" as copier;
let service = new MyService();
let bucket = service.tryFindChild("Storage");
new copier.FileCopier(bucket); // error: expected Bucket, but received IConstruct
They discover unsafeCast
lets you cast between types, so they fix it like so:
// snip
new copier.FileCopier(unsafeCast(bucket));
unsafeCast
was used in the context of FileCopier
which explicitly expects a Bucket
, so it was cast to a Bucket
and the code compiles now. Cool 😎
A few weeks later, the user upgraded their dependency, compile their project, deploy it, and the file copier stops working, but they can't tell why.
It turns out, FileCopier
had a breaking change, and it now accepts a Redis
instead of a Bucket
. But the user didn't have to specify the type they're casting to, so their code continued to compile.
If they were required to specify a target type, this bug wouldn't have been possible since the error would have be caught at compile time:
new copier.FileCopier(unsafeCast<cloud.Bucket>(bucket)); // error: expected Redis, but received cloud.Bucket
WDYT?
Thanks for the example, I can now see the importance of explicitly specifying the casting target.
So now we need to decide what's the preferred syntax. @Chriscbr curious if you have a preference.
I am wondering if we should reconsider the as ID
syntax? It's quite ugly and it's not uncommon to use it, so maybe we can revisit that and use as Type
for casting...
Perhaps a string between the type name and the ()
in a new expression would work better for the id
:
new cloud.Bucket "dear-bucket" ();
It's a quirky but it has a kind of feels like the id is the first implicit argument of the constructor...
@eladb yeah, I'm totally open to alternatives to as ID
- definitely something worth playing around with.
Do we use "#" for anything? I sometimes associate #name
with IDs because of CSS, maybe there's something we could do with that...
Another idea I had was:
new cloud.Bucket() named "Storage";
Re: casting, I talked with @MarkMcCulloh and other folks on the compiler team, and it was mentioned it could be OK to use the generic type param syntax unsafeCast<T>(value)
or cast<T>(value)
as an incremental solution
Not a huge fan of this syntax but I am okay to try it n
Hi,
This issue hasn't seen activity in 60 days. Therefore, we are marking this issue as stale for now. It will be closed after 7 days. Feel free to re-open this issue when there's an update or relevant information to be added. Thanks!
Hi,
This issue hasn't seen activity in 60 days. Therefore, we are marking this issue as stale for now. It will be closed after 7 days. Feel free to re-open this issue when there's an update or relevant information to be added. Thanks!
Personal preference would be to something that is visibly clearly showing the casting. So last option would be best: cast<T>(x)
Although I have seen and like the specific approach for the base type to have their own cast Str(x)
, Bool(x)
and so on.
@cast<T>
works for me (the @ denotes that this is a builtin)
(and perhaps we can replace unsafeCast
with @cast<any>
which is practically what it is)
I'm for changing it to @cast
as long as the user's required to specify the type they're casting to. We'd need to add support in the compiler to parse <type>
clauses in intrinsic function calls but it feels doable.
I think the ergonomics as T
is still pretty convenient, so it might also be worth considering as a shorthand. As long as casting in Wing has no runtime cost, I think it would lower cognitive load for new users if Wing adopts the syntax already used by TypeScript and Rust. (I recognize cast
being a function is common in some other languages like C++, but in terms of language adoption I think more folks adopting Wing are probably coming from TypeScript).