-
Notifications
You must be signed in to change notification settings - Fork 15
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
Make export default class the main file class in GDScript, add support for subclasses #61
base: main
Are you sure you want to change the base?
Make export default class the main file class in GDScript, add support for subclasses #61
Conversation
…low for files without default class and not exported classes
828bbb8
to
1e9f87f
Compare
…classes-in-one-file
Reading through the examples in the PR (which are very helpful, thank you), is also
supported/should it be? |
It should be, but I think this could go as another PR because this one is already very big :) |
Y'know, I didn't realize that you could import inner classes from other files, and I think that was one of my biggest confusions with why you wanted inner classes - they just seemed strictly worse :) Now that I've clarified that to myself, I'm officially on board with this PR - the generated code just makes more sense than before! This PR also looks extremely thoughtful with all the cases you checked. Two high level questions/suggestions about this before we merge it:
I think this would also help ease discoverability of this feature. I think my biggest worry is that new developers to ts2gd with TS experience would mark something as
In general I'd also be keen to know how circular references pan out for multiple classes being defined in the same file. We should make sure it's not possible to cause that Godot error at all with this PR. Anywho, TL;DR - this is fantastic, just want to make sure it's logical to our users and that it doesn't cause any circular errors. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here's a quick preliminary CR
|
||
extendsFrom = type.getText() | ||
|
||
if (tsType.symbol && tsType.symbol.declarations) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it actually be helpful to build a map of "class" to "is inner or normal" as a pass before codegen? And then we could just pass that into all the nodes in ParseState?
Mostly just asking out of curiosity, I can't really tell.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think one advantage of this is it'll help you reason about the whole "single class in a file is automatically default" case that I was suggesting without having to put that if statement everywhere... :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know if "single class in a file is automatically default" is a good idea because what if someone wants a .gd
only with one inner class? (asked the same question in my comment below).
Would it actually be helpful to build a map of "class" to "is inner or normal" as a pass before codegen? And then we could just pass that into all the nodes in ParseState?
About that I think we could improve it in the future but currently there is no need to do this. I don't want to make this PR even more complicated :)
Thank you! I'm really happy that it makes sense not only for me :)
I used
What if you want to have a single class in file and you want it to be inner class?
We could do that but that will always force people to have a default class. That does not match Godot code which allows a
Instead of
I will research about the circular references and check if they are affecting genrated code. I've never had a problem with them in Godot so I need to get more info. |
Do we want to support library/util code without a class? Is that changing with this PR in some way? Regarding circular references, in TS land you can run into them if you have code running on file load. If you have a circular reference on of the files will see the other's exports as undefined (without the types refelecting that possibility). I don't know how GD handles these though. TS solution is to not run anything on file load and instead wrap in a closure and then call that by some other file. |
@ksjogo I meant code like this below. Where you create // src/utils.ts
// There is no class with `export default` what means that you cannot create a node with script attached from this file
export class MyUtil1 {
utilFunc1() {
return "a"
}
}
export class MyUtil2 {
utilFunc2() {
return "b"
}
} # compiled/utils.gd
# notice no class_name or no extends, but inner classes still can be used by preloading
# `preload("res://compiled/utils.gd").MyUtil1` in ohter `.gd` files
class MyUtil1:
func utilFunc1():
return "a"
class MyUtil2:
func utilFunc2():
return "b" |
Can you check the generation of the dynamic definition files? Are files now required to have that default export? Additionally, when I change these to have default export, the interface name wrongly gets generated as e.g. |
Yes I will look into that later today.
Yes it is required if you want to generate a class with |
@ksjogo Fixed generating |
I looked into circular references and basically Godot properly handles I think ts2gd should have the same behavior: if someone creates circular reference in TS then this is theirs problem and ts2gd should not fix it for them (I refer to extracting enums to separate files). Of course this will force developers to be more aware of the code they write, but in the end when you writing TypeScript to JS you can also create circular references. In ts2gd it will be just more restricting. To fix such error in TS you can just move your enums to another file and this way we keep 1:1 |
Just an update that I've been busy with work this week but I'll look into merging this soon :) |
…for-multiple-classes-in-one-file
README.md
Outdated
|
||
```gdscript | ||
class InnerClass extends Node2D: | ||
func _init().(): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The .() at the end seems incorrect.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This means that a base constructor is called (here are docs). If base constructor does not have arguments .()
can be omitted. Why is it wrong? Does this code not compile or is wrongly generated?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I know it looks weird but it really is valid GD syntax!
Correct. We always need an explicit way to mark the class as main if the user has more than 1 class.
I just can't understand why a user would ever want to have only inner classes. Is this a common pattern? I admit to being a bit of a gdscript novice... but I very rarely use inner classes at all, and I have never had a script with only inner classes. :P The reason I bring this up is because I think that if ts2gd has to compromise between making an uncommon case annoying or a common case annoying, we should choose to make the uncommon case annoying. For instance, if people want to write a single inner class, but if it's only 1% of all people, I'd be totally fine with requiring a On the other hand, I feel that "one class per file" is extremely common, and we should make that case work without users even having to think about it. This is why I think people should be able to write
Yes, docs are good, but errors are better - and best is users that just write the right thing without even having to think about it. Yeah, some users are going to read the docs, but a lot of them are busy and don't have a lot of time to pore over everything we've written. The most important thing, though? My guess is that, for new users to ts2gd, writing a single class in a file is likely the very first thing they will do. Ideally they can do this - and have it work in Godot - without even looking up the documentation at all. :) (I do admit that some aspects of ts2gd are tougher to guess without checking the docs... That is the ideal, however!) (Although I do agree we need more example code :) I should honestly just publish some of my personal projects I've written with ts2gd...)
So the thing I think here is, in TS, these patterns would not cause circular reference errors. So, experienced TS programmers writing natural TS code and getting circular reference issues would actually be an unpleasant thing. Also, on a personal note, I find circular references are extremely annoying. If there are easy ways to avoid them in generated code, we should try to do that :) If it's very hard, perhaps we could just warn on them. But I've definitely run into circular refs when writing gdscript and it's super annoying. I hope it gets fixed in gdscript 2... EDIT: I checked and using load() instead of preload() avoids circular errors. So we can just codegen load - I don't see any disadvantage. |
All that said, here's my proposal: Single classTS:
GD:
Two classes, no defaultTS:
GD:
Two classes with defaultTS:
GD:
Single inner classTS:
GD:
|
Okay, this way works for me. Before I start reimplementing, one last question. @johnfn Do we want to use |
…classes-in-one-file
…main by default, non exported classes are always inner
I've changed the implementation to mostly match your proposal. I've added |
This is looking pretty good. With the writing tweaks I think we're good to go here. I do have one suggestion, however. I feel like we're providing users a little too much choice between With that in mind, how do you feel about removing the I think we do need to preserve I know this diff has been outstanding for a while, so if you want to just merge it now and fix that in a follow up, that makes sense to me :) Thanks for the awesome diff. Can't wait to land this! |
More changes to this PR right now will make it even more confusing. I agree that we should address removing I've done fixes that you requested in the review, there are two not resolved comments that you can look at. I think everything else is now ready to merged. |
@johnfn Hey, will you find some time to review and merge this? :) |
In addition to issue #59. This the proof of concept of my idea.
closes #59
closes #1
Main feature - export default is now main class, allow inner classes
1. First case
Creating a script that can be attached to a node in Godot
Generating:
Importing from TypeScript in another file
2. Second case
Creating a script that can be attached to a node and have some helper classes
Generating:
Importing from TypeScript in another file
3. Third case
Creating a script that cannot be attached to a node in Godot, but have helper classes that can be imported to other scripts.
Generating:
Importing from TypeScript in another file
Additional features
Extending inner classes
Generates
Anonymous export default class
Generates
Extending anonymous classes
Generates
Inner classes super calls
Generates
Enums no longer generate separate files
Generates