rhai icon indicating copy to clipboard operation
rhai copied to clipboard

Type hints

Open tamasfe opened this issue 2 years ago • 13 comments

I'm in the middle of writing the LSP server I mentioned in #268, and in order to provide more useful information (such as field completions), some kind of type system is essential. Right now I'm planning HM-style type inference and external type definitions for modules just like it is in TypeScript's .d.ts files, but allowing users to define types for function signatures and let/const bindings inline would be very useful.

Would it be possible (acceptable) to add optional type hint syntax in Rhai? I am thinking along the lines of Python's type hints, or a much simpler version of TypeScript's type system.

Type hints would only serve the users and static analyzers, and they could be completely stripped when a script is compiled.

I don't have exact fleshed-out proposal for this yet as I will have to experiment more in the LSP project first, but I would like to see something like this supported in Rhai in the future.

tamasfe avatar Nov 15 '21 03:11 tamasfe

I think something like TS's .d.ts will be great for this purpose. The user simply loads a .d.whatever within the search path (e.g. same folder as the Rhai scripts) which includes signatures for all functions and modules registered into the Engine (possibly with documentation). Possibly definition of external types also.

Beware that any function call is likely to come up with multiple hits due to overloading; unless your server is very smart, it is probably not feasible to determine statically what the types of the arguments are, and so it may hit more than one version of the same function.

Type hints are possible but do you really want to go there? Implementing an entire type system in an LSP is not a trivial task... You'd probably do well enough just by providing standard features like "go to definition" for variables and functions, detection of dead code and/or unused variables, etc.

schungx avatar Nov 15 '21 04:11 schungx

I think something like TS's .d.ts will be great for this purpose. The user simply loads a .d.whatever within the search path (e.g. same folder as the Rhai scripts) which includes signatures for all functions and modules registered into the Engine (possibly with documentation). Possibly definition of external types also.

Yes, this is something I'm definitely planning to do, inline type hint proposal would be a follow up based on it.

Beware that any function call is likely to come up with multiple hits due to overloading; unless your server is very smart, it is probably not feasible to determine statically what the types of the arguments are, and so it may hit more than one version of the same function.

I'm aware of this, I'm currently looking into ways to handle it in HM properly. Determining the types of this is quite interesting as well.

Type hints are possible but do you really want to go there? Implementing an entire type system in an LSP is not a trivial task... You'd probably do well enough just by providing standard features like "go to definition" for variables and functions, detection of dead code and/or unused variables, etc.

I would definitely like to have some kind of type system because I'm so used to it as a developer and I hate it when an IDE goes "welp, you have this ... something that come from ... somewhere... good luck!". As for the LSP, I think this is the next major step as implementing function signature help or field completion without types while possible, feels really hackish and fragile.

So my motivation is definitely there, whether I'll have the time and dedication to implement all of this is a good question though.

tamasfe avatar Nov 15 '21 04:11 tamasfe

My suggestion is to first have an LSP with standard functionalities. Have it work nice and well.

Then start adding new things to it, such as type hints...

Typing for dynamic scripting languages is a highly non-trivial matter, and I think you should avoid chewing off too large a piece. Better nibble at it bite by bite...

schungx avatar Nov 15 '21 05:11 schungx

I think you're going to want type hints eventually anyway, just because it makes programming so much more tractable. Basically every dynamically typed language that gains any kind of popularity has to add them eventually.

You might not want to bother implementing them yet but I think it would be worth sketching out the syntax so you can add them later without issue. E.g. Typescript has some awkward syntax with function types due to the => syntax, and Python has issues with forward declaring types.

Implementing an entire type system in an LSP is not a trivial task

I agree. It should arguably be part of the compiler. You can't really avoid the work somewhere though.

Beware that any function call is likely to come up with multiple hits due to overloading; unless your server is very smart, it is probably not feasible to determine statically what the types of the arguments are, and so it may hit more than one version of the same function.

Typescript has the same problem and it's not really an issue because the type annotations say what type the arguments are.

One final thing - you'll need to support type hints in the file (not just in .d.ts) because a) it's a much better experience and b) you can't annotate stuff in function bodies (local variables) otherwise.

Ps: great project. I've used it in a little prototype tool as a programmable config file and so far it works well!

Timmmm avatar Jan 30 '22 19:01 Timmmm

I am thinking along the lines of Python's type hints

On that note:

  • https://threeofwands.com/python-is-two-languages-now-and-thats-actually-great/
  • https://cs.brown.edu/~sk/Publications/Papers/Published/lgmvpk-static-python/

erlend-sh avatar Apr 08 '23 08:04 erlend-sh

That second link is quite interesting. I don't think I've seen a language with gradual soundness before. Though a 3.7% performance increase in Python is about as bad a result as I could imagine.

I think Dart is probably the most interesting language to take inspiration from when it comes to gradual typing and soundness, since Dart 1 was gradually typed and unsound, and Dart 2 is now fully statically typed and sound. The soundness does actually cause some big inconveniences compared to e.g. Typescript which is not sound.

Those were caused by language choices that they can't change now so if Rhai wants to do sound static typing then it's definitely worth doing as soon as possible.

Timmmm avatar Apr 08 '23 14:04 Timmmm

Well most of these languages do not assume to work closely with another, compiled language, so their needs are more pronounced as they must be reasonably self-contained.

Rhai can cheat out by moving most typing needs to Rust...

So there is not a definite indication how useful strong typing will be...

schungx avatar Apr 08 '23 15:04 schungx

I also think that this idea of using type hint in Rhai is very good. On the one hand, you dont really need it, because of rust types, but on the other hand, Rhai can be used as standalone scripting/modding language for projects written in rust. For example for game engines. Real-life example, is Godot. You can code with gdscript, c++ etc. You could use gdscript for only dynamic typed scripts and c++ for statically, but would you? In most scenarios you dont really need the speed of c++ for game logic, but you still need you code to be typed, it makes everything more readable and much more safe. Thats why gdscript implements typing. Thats what i think

pyranota avatar Oct 04 '23 11:10 pyranota

Well, it is actually quite easy to put typing into Rhai... typing removes the possible number of states the program can be in, therefore it is a restriction and thus easy to implement.

The hairy thing is not to restrict too much. For a dynamic language, one would expect to inter-mix different types (for example, returning () for not-found is a good example... the value and () would be two distinct types).

Therefore, for useful typing, you need some form of union typing like TypeScript, and possibly some form of algebra to express the allowed set of types. Thus this typing system, which is distinct from Rhai by the way, is the problematic part.

schungx avatar Oct 05 '23 00:10 schungx

Therefore, for useful typing, you need some form of union typing like TypeScript

Yeeeahh... Thats the good idea. For example:

fn foo(a: bool) -> () | bool{
   if a {
      return true;
   } else {
      return ();
   }
}

pyranota avatar Oct 05 '23 08:10 pyranota

Or:

fn foo(a: bool) -> bool? {
   if a {
      return true;
   } else {
      return ();
   }
}

schungx avatar Oct 05 '23 13:10 schungx

As for this keyword, Rhai could use this:

fn foo(this: type1 | type2){
   // todo
}

pyranota avatar Oct 05 '23 17:10 pyranota

Yeah I think basically copying Typescript is probably the way to go, including any, unknown, as hoc sum types, interfaces, generics, etc. You can probably stop short of some of the more advanced features.

The only thing I would really change from Typescript is that discriminated unions are very tedious to define, but fixing that properly requires it to be an actual language feature rather than just type annotations.

Timmmm avatar Oct 05 '23 18:10 Timmmm