Open Feuermurmel opened 1 year ago
Definitely on my list of things to do. unknown
would be the highest priority. never
would definitely be nice to have as well, but there's a whole heap of control flow stuff that's currently missing and without it never
isn't all that useful.
There is at least kind of a never type for general use, but in the case of a function it implies either return
(without a value) or the absence of return
, which is subtlety different from a function that returns nil
. You can also get at the void
type implicitly. At runtime it'll actually be nil
, but it's useful for us when writing code to have a distinction between something that's intentionally nil
and meant to be nothing.
---@type string
local aString
local implicitVoid = --[[---@not string]] aString
---@type void
local explicitVoid
explicitVoid = implicitVoid
Ah, I see. So @not
can be used for the same as TypeScript's Exclude
. Somehow it's behaving a bit strange. It works in the example you have, with a separate variable of type string
, but it does not work on a string literal:
And as you explained, void
does not have the properties that one would expect from a never
type:
That first case is obviously incorrect and is something I'd like to fix at some point. For the most part it comes down to the fact we don't have intersection types, as you've noted elsewhere.
For that 2nd example though, what behaviour would you expect?
TypeScript will allow the assignment with never
:
but I'd argue that's incorrect, or at least, less useful to developers.
That said, Luanalysis can be tricked into doing something similar:
---@type string
local aString
---@type void
local explicitVoid
aString = explicitVoid
aString = math.random() and "hi" or explicitVoid;
Basically if you X | void
the result is X
, which is the same behaviour as TypeScript and generally correct.
The difference is that Luanalysis forbids assignment of "nothing" to "something" which I'd argue is more useful. When we get into set theory (e.g. unions) it disappears though as it should.
Hey, it took a bit of time to come back to this and answer your questions. After opening the few issues on this project, I quickly decided to drop it and move to something else (TypeScriptToLua). So I'm not invested in these issues anymore, but I'll try to answer your questions.
For that 2nd example though, what behaviour would you expect?
It depends on what the intention for void
is. Generally in the type systems I've seen it's one of two things:
With (a), allowing void to be used as a type is confusing, so ---@type void
on a variable should be flagged as an error, which makes the question of assignment irrelevant. All usages of the return value of such e function should be flagged as an error.
With (b), the unit type should be assignable like any other type.
TypeScript will allow the assignment with
never
:but I'd argue that's incorrect, or at least, less useful to developers.
as
is an unchecked cast. IMHO the problem with the example code lies there. After that, all bets are off anyways. I'd argue the syntax for an unchecked cast is much too light in TypeScript and looks like something on-dangerous.
I've written a lot of code in langauges that treat the bottom type as a normal type (e.g. Scala) and in my experience, these types are so rare that they're never a problem. I wouldn't fret about it too much and start off with whatever the math tells you is correct. Adding more warnings/errors is always possible later (this is what TypeScript does a lot, first do it correct, and then make it more useful if the same mistakes are made repeatedly).
Basically if you
X | void
the result isX
, which is the same behaviour as TypeScript and generally correct.The difference is that Luanalysis forbids assignment of "nothing" to "something" which I'd argue is more useful. When we get into set theory (e.g. unions) it disappears though as it should.
This makes me assume that void
should be considered a function annotation, in which case, yes, the assignment should not be possible, but a type X | void
should not be possible ether (void
being a function annotation and not a type).
On a personal note, the way this plugin treats any
and void
is the reason I abandoned this project in the end. It just gave me the feeling that these "types" weren't going the be treated consistently and that I won't be able to rely on my intuition, ultimately leading to more mistakes on my part.
I'd rather have a type systems that follows some mathematical reasoning, where I've built a lot of intuition from other languages, and then adds some helpful, additional safeguards on top.
I hope I'm understanding the current intended semantics of
any
correctly. To my understanding,any
is a type that is meant to be assignable to any other type and also from any other type. It is used in places where the real type isn't known or where it would be too cumbersome to write it down. Similar toany
in TypeScript andAny
in Python.What I'm mainly missing is a Top type, i.e. a type that is assignable from any other type, but not vice-versa. This would be useful in cases where the type a type is not known and where the analyzer should check that every possible type is handled. This is what Python's
object
and TypeScript'sunknown
is.And by extension, a Bottom type could be added, i.e. a type that is assignable to any other type, but not vice-versa. This would be useful in cases that are expected to never happen, e.g. as return type of a function that never returns or that always panics. This is what Python's
Never
(alias ofNoReturn
) and TypeScript'snever
is.