Skip to content
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

Store FQN information throughout compiler #7041

Closed
Chriscbr opened this issue Aug 22, 2024 · 4 comments · Fixed by #7047
Closed

Store FQN information throughout compiler #7041

Chriscbr opened this issue Aug 22, 2024 · 4 comments · Fixed by #7047

Comments

@Chriscbr
Copy link
Contributor

Chriscbr commented Aug 22, 2024

In Wing, user-defined types (like classes and enums) are automatically organized hierarchically based on the directory structure of the user's source code. As a human-readable way to understand the location of types (and correspondingly, how to import them), we associate each type or namespace with a "FQN" or fully-qualified name. For example, the Bucket class that comes included in the Wing cloud library has an FQN of @winglang/sdk.cloud.Bucket. The FQN is a dotted string formed by the name of the library, the names of any namespaces it belongs to, and the name of the type.

To implement the language so far we didn't need to store or manage this information in the compiler, but we're reaching a point where not having FQNs is starting to cause friction. One example is that the Wing platform system heavily depends on using FQN strings in preflight code, so we're limited by what code the compiler can generate today. Another example is that for generating docs, we'd like to know the FQN of every type and of every namespace.

To ensure we generate unique and correct FQNs throughout a Wing compilation, it seems like the best place to store this FQN information is directly within the structs in our compiler that correspond to namespaces and types. Some challenges I anticipate are described below.

Name conflicts

Today, Wing allows you to have two classes, interfaces, or enums with the same name in a given namespace, by defining them in sibling files within the same directory. An error is only raised if the parent directory is imported with a bring statement and both types are public. [1] Even when the error is avoided (by making one of the types non-public or not bringing the directory), it's possible the type system would end up associating these types with the same FQN, which ought to be avoided.

Potential solutions:

  1. Do not generate FQNs for non-public types. In Rust, we'd have to store the FQN as fqn: Option<String>. (Namespaces aren't affected by naming conflicts, so they can still have fqn: String).
  2. Associate non-public types in the compiler with FQNs that are differentiated in some way (e.g. @winglibs/momento.InternalClass#0294db - here, the hash is generated based on the name of the source file).
  3. Disallow user-defined types in the same directory from having the same name so that we can guarantee each type can be given a unique FQN. I think this is fine in terms of design, but it might result in a less consistent experience due to various edge cases:
    • When you run wing compile main.w, the compiler doesn't try to read and parse all files in your current directory; it only reads the main.w file and any files it depends on. This means an error might not be raised if one of the files is currently unused. It's also possible you might get an error when you open both files in your IDE, but when you compile the project no errors are raised.
    • If we detected two classes in different files with the same name, it could be tricky to ensure an error is reported on both classes.

Another edge case to consider is that the compiler technically has some limited support for nested classes right now. FQNs for such classes may be ambiguous or not serve a concrete purpose.

[1] Another scenario is that the parent directory is packaged as a Wing library and both classes are public.

Implementation approach

One snag we might hit is that we generally type check files in reverse-dependency order -- that is, if file A brings in types from file B, we type check file B before file A. This makes things tricky for us since that generally means the deepest files in our project are type checked first, and thus we need to calculate the FQNs for types in those files first.

Possible solutions:

  1. Calculate the FQN of types based on a source file's relative path from the root of the project or library it's in. This means we need the parser to calculate what the root file of each project or library is so we can use it at type checking time.
  2. Have the parser calculate a FQN for every source File. During type checking, we have information about what File we're type checking, so we can just retrieve the base FQN and append the name of the class or enum we're processing to calculate the FQN.
@Chriscbr
Copy link
Contributor Author

Chriscbr commented Aug 22, 2024

I think if we kill nested class support for now it might allow us to simplify a lot of details in the compiler. Feels like an appropriate tradeoff to make at this stage of the project. Then we can assume every type has a FQN (option 2 in the "Naming conflicts" section).

@Chriscbr
Copy link
Contributor Author

Actually, killing nested class support can't be done right now since the compiler currently translates all inflight closures into fresh class definitions. That's also something we ought to change, but I'd like to avoid pulling on that thread for the moment.

@monadabot
Copy link
Contributor

Congrats! 🚀 This was released in Wing 0.83.6.

@eladb
Copy link
Contributor

eladb commented Aug 27, 2024 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Done
Development

Successfully merging a pull request may close this issue.

3 participants