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

Cyclic type definitions #33

Merged
merged 19 commits into from
Nov 27, 2024
Merged

Cyclic type definitions #33

merged 19 commits into from
Nov 27, 2024

Conversation

JohannesMeierSE
Copy link
Collaborator

@JohannesMeierSE JohannesMeierSE commented Nov 12, 2024

This PR fulfilles #22 ...

  • for Classes and Functions (other types are still missing, but should be straight forward)!
  • various test cases
  • lots of bug fixes
  • @Lotes It is not required to explicitly call "resolveTypesNow" or something like that 🙂
  • introduced Identifiers (different than Names)
  • validation (instead of exception) for super-sub-class cycles

Hints for the review

  • Do you have ideas for more (test) cases with cycles or difficult topological ordering of types?
  • What do you think about the general architecture?
  • Reviewing this PR commit by commit might work not that good, since I reworked the cycle handling logic several times.

Future work in separate PRs:

  • support the remaining types
  • double-checking, some parts don't feel 100% yet

Copy link
Collaborator

@Lotes Lotes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will give another trial on Monday.

packages/typir/src/features/inference.ts Show resolved Hide resolved
packages/typir/src/utils/utils-definitions.ts Show resolved Hide resolved
packages/typir/src/graph/type-node.ts Show resolved Hide resolved
packages/typir/src/graph/type-node.ts Show resolved Hide resolved
packages/typir/src/graph/type-node.ts Outdated Show resolved Hide resolved
packages/typir/src/utils/utils-definitions.ts Show resolved Hide resolved
Copy link
Member

@insafuhrmann insafuhrmann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow, @JohannesMeierSE, this is really a heavy PR. Thanks for all the work you invested! I think it is pretty hard to grasp everything in just one pass. I left a number of detail remarks from my pass, though. I like the overall architecture, my only worry is about performance with the repeated checks of the waiting crowd, but as you say, the number of types is not related to the length of files. We should still keep an eye, I think.

examples/lox/test/lox-type-checking.test.ts Show resolved Hide resolved
packages/typir/src/utils/test-utils.ts Show resolved Hide resolved
}
class B { }
`, [ // Typir works with this, but for LOX these validation errors are produced:
'Declared classes need to be unique (A).',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like that you thought of this case and the possibility of duplicates happening during the wait. I wonder about a validation like this and its relationship to the type initialiser that ensures a type is registered only once. Do we simply have both? In the sense of: Should a language normally have this validation, but whether it has or not, we make sure in the background that there is no double entry?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should a language normally have this validation, but whether it has or not, we make sure in the background that there is no double entry?

Yes, exactly!

Typir ensures, that types are existing only once in the type graph.

The (predefined) validations check, that there are not multiple AstNodes which declare the same Typir type.

examples/lox/test/lox-type-checking.test.ts Show resolved Hide resolved
`, []);
expectTypirTypes(loxServices, isClassType, 'A', 'B');
expectTypirTypes(loxServices, isFunctionType, 'myMethod', ...operatorNames);
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Somehow this (that being a member of a specific class does not affect the type of a method) feels weird to me though I cannot really explain it. I think it is correct, thinking of the type checking purposes, but the feeling comes from the different contexts (visibility of fields, different potential side effects). No issue though. Just a remark.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right. I postponed the support for methods in classes for a while, since for me, the realization was not obvious as well.

I guess, we could realize methods also in a different way. The final reason for me to use Function types as well was to have a simple solution and to reuse all the implementation for functions.

In order to to make annotating further properties to methods easier (which are not supported by functions), I described methods of classes in the following way:

export interface MethodDetails {
    type: TypeReference<FunctionType>;
    // methods might have some more properties in the future
}

export class ClassType extends Type {
    // ...
    protected methods: MethodDetails[];
    // ...
}

Maybe, MethodDetails will get more properties in the future, or we change the whole approach to describe methods, I am open for both.

packages/typir/src/kinds/class-kind.ts Show resolved Hide resolved
packages/typir/src/kinds/class-kind.ts Outdated Show resolved Hide resolved
packages/typir/src/kinds/class-kind.ts Outdated Show resolved Hide resolved
packages/typir/src/kinds/class-kind.ts Outdated Show resolved Hide resolved
packages/typir/src/utils/utils-definitions.ts Show resolved Hide resolved
Identifiable --> Invalid
```
*/
export type TypeInitializationState = 'Invalid' | 'Identifiable' | 'Completed';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same for me, why do we need Identifiable? For cycle resolution you only need waitingFor and resolved. Maybe it makes sense to extract this algorithm and solve in isolation.

@JohannesMeierSE JohannesMeierSE marked this pull request as ready for review November 27, 2024 09:54
@JohannesMeierSE
Copy link
Collaborator Author

I pushed the last fixes according to your review. The suggested additional test cases were very helpful to find some more bugs! Thank you very much for your reviews @Lotes and @insafuhrmann!

@@ -562,7 +571,7 @@ export class ClassTypeInitializer<T = unknown, T1 = unknown, T2 = unknown> exten
this.initialClassType = new ClassType(kind, typeDetails as CreateClassTypeDetails);
if (kind.options.typing === 'Structural') {
// register structural classes also by their names, since these names are usually used for reference in the DSL/AST!
this.services.graph.addNode(this.initialClassType, kind.getIdentifierPrefix() + typeDetails.className);
this.services.graph.addNode(this.initialClassType, kind.calculateNominalIdentifier(typeDetails));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is structural, but uses the nominal identifier?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I renamed the function, since its name was misleading: When used in languages (e.g. TypeScript), structurally typed classes are used by other classes not by encoding their structure but by using their "names". Therefore "name-based identifiers" are relevant for structurally typed classes as well.

@JohannesMeierSE JohannesMeierSE merged commit 5ab0276 into main Nov 27, 2024
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants