Open Narazaka opened 3 years ago
I was thinking of adding a cast syntax, and here is a draft of the proposal. Any thoughts? https://hackmd.io/oUkkX_jqS5W_5mvvpN--2A
I have one question.
When I mentioned above that the @as
chain is required, I was assuming that type compatibility would be checked in this typecast as well.
For example, in TypeScript, {} as {a: number}
can be cast without warning, but 1 as string
will cause a warning unless you write 1 as unknown as string
.
Is there a type compatibility check for such typecasting, is there one now, or will there be one in the future?
If there is a possibility of checking, I think we need a syntax for chaining @as
. This is because there should be a workaround for modules released with the wrong type.
However, I think the current syntax can be extended to, for example, # @as untyped @as String
. If such an extension is allowed in the implementation, I think it would be fine to release it with the current syntax definition as is, without any type checking planned at this stage.
The typecasting of values makes it possible to ignore probably almost all implementation type errors, even when strictly typed in rbs. I think this would be a real "incremental" type benefit. I'm looking forward to it.
We will implement a type checking between before-after types with @as
syntax.
The reason why we need chained-casts is that we often want casts between irrelevant types, right? And I believe introducing another unchecked casting syntax, @as! T
or @as unchecked T
, would solve the problem.
Yes, that's the use case I was thinking of. I think the cast without type checking you suggested would certainly solve the problem!
This would be very helpful. Right now I have a method that has a return like my_string[0, 12]
this will always return String
since the starting index is 0. However, the rbs types see it as return String | nil
. The only current workaround I see is to do my_string[0, 12] || ""
even though the ||
will never get hit.
I think Steep needs "value casting", which is equivalent to
as
in TypeScript.@as
is an annotation for a new "value typecast" rather than the currently existing "variable definition modification" (@type var foo: T
).The
@as
chain should also be possible.A concrete use case is to construct a hash that is sure to contain a particular key without any initial value.
In the following Foo#bar, we want to return a hash
{a: Integer, b: Integer}
of the result of the calculation for each default key. (This is a simplification of the original implementation ofActiveSupport::Duration.build
)I think it's reasonable that the type should look like this
But this will of course throw an error, because
parts
will beHash[untyped, untyped]
.So add type annotations to parts.
But now
{}
is Hash[untyped, untyped], soparts = {}
throws an error.This can be solved by putting an initial value like
parts = {a: 0, b: 0}
, but this is not a natural implementation.It is not appropriate to change the implementation for types in such obvious cases, and it would be ideal to be able to type with
parts = {}
.Gradual typing into dynamically typed languages is for humans, so I think the very annoying hassle of "having to tweak the implementation to get rid of type errors" defeats the purpose. (changing the implementation may require retesting!)
TypeScript provides a workaround for such a case by using
as
, which can cast to a value even if it is incorrect.A similar implementation in TypeScript can be written as follows
As in ruby, the error is that
{}
(roughly equivalent toHash[untyped, untyped]
) and{a: number; b: number}
are mismatched.You can also specify the variable type of
parts
as a workaround.However, as with Ruby, you will get an error that
{}
cannot be assigned toparts
.However, TypeScript has
as
.This will turn
{}
into{a: number; b: number}
(which is different from what it actually is), and there will be no errors.In addition, TypeScript type checking supports the
as
equivalent in the form of/** @type {T} */(val)
, even if type annotations are added as in JavaScript syntax.In Ruby, range comments are difficult to use, so it is difficult to define them in the same way, but I think it is possible to solve the problem by adding annotations to the value side as follows.
Note that
as
in TypeScript cannot be converted to completely incompatible types. However, it is often the case that a completely incompatible castmystring as any as number
(in JavaScript,/** @type {number} */(/** @type {any} */(mystring))
) is required due to, for example, incomplete type definitions in libraries. If this is not possible, there will be no workaround for typing errors in the library.So, if you have a similar specification for
as
, you also need a chain ofas
, such asmyvar as any as number
, which should be defined in two consecutive lines.