-
Notifications
You must be signed in to change notification settings - Fork 5
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
Changes from 3 commits
a2bb2ee
bf1eb78
36708f0
c1f02ec
0c933fa
ddda324
6c29f57
40bea25
d4b1cde
005c37f
072de08
704d81c
a07614b
14b21d5
db66fba
9f1b703
2a6c18f
3b471e2
bc43521
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,7 +30,9 @@ export class ClassType extends Type { | |
protected methods: MethodDetails[]; // unordered | ||
|
||
constructor(kind: ClassKind, typeDetails: ClassTypeDetails) { | ||
super(undefined); | ||
super(kind.options.typing === 'Nominal' | ||
? kind.calculateNominalIdentifier(typeDetails) // use the name of the class as identifier already now | ||
: undefined); // the identifier for structurally typed classes will be set later after resolving all fields and methods | ||
this.kind = kind; | ||
this.className = typeDetails.className; | ||
|
||
|
@@ -388,6 +390,12 @@ export interface CreateFieldDetails { | |
type: TypeSelector; | ||
} | ||
|
||
/** | ||
* Describes all properties of Methods of a Class. | ||
* The final reason to describe methods with Function types was to have a simple solution and to reuse all the implementations for functions, | ||
* since methods and functions are the same from a typing perspective. | ||
* This interfaces makes annotating further properties to methods easier (which are not supported by functions). | ||
*/ | ||
export interface MethodDetails { | ||
type: TypeReference<FunctionType>; | ||
// methods might have some more properties in the future | ||
|
@@ -444,8 +452,6 @@ export class ClassKind implements Kind { | |
assertTrue(this.options.maximumNumberOfSuperClasses >= 0); // no negative values | ||
} | ||
|
||
// zwei verschiedene Use cases für Calls: Reference/use (e.g. Var-Type) VS Creation (e.g. Class-Declaration) | ||
|
||
/** | ||
* For the use case, that a type is used/referenced, e.g. to specify the type of a variable declaration. | ||
* @param typeDetails all information needed to identify the class | ||
|
@@ -523,6 +529,10 @@ export class ClassKind implements Kind { | |
} | ||
} | ||
|
||
calculateNominalIdentifier<T>(typeDetails: ClassTypeDetails<T>): string { | ||
return this.getIdentifierPrefix() + typeDetails.className; | ||
} | ||
|
||
getMethodKind(): FunctionKind { | ||
// ensure, that Typir uses the predefined 'function' kind for methods | ||
const kind = this.services.kinds.get(FunctionKindName); | ||
|
@@ -546,7 +556,6 @@ export function isClassKind(kind: unknown): kind is ClassKind { | |
} | ||
|
||
|
||
// TODO Review: Is it better to not have "extends TypeInitializer" and to merge TypeInitializer+ClassTypeInitializer into one class? | ||
export class ClassTypeInitializer<T = unknown, T1 = unknown, T2 = unknown> extends TypeInitializer<ClassType> implements TypeStateListener { | ||
protected readonly typeDetails: CreateClassTypeDetails<T, T1, T2>; | ||
protected readonly kind: ClassKind; | ||
|
@@ -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)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is structural, but uses the nominal identifier? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
} | ||
|
||
this.inferenceRules = createInferenceRules<T, T1, T2>(this.typeDetails, this.kind, this.initialClassType); | ||
|
@@ -588,8 +597,9 @@ export class ClassTypeInitializer<T = unknown, T1 = unknown, T2 = unknown> exten | |
|
||
if (this.kind.options.typing === 'Structural') { | ||
// replace the type in the type graph | ||
this.services.graph.removeNode(classType, this.kind.getIdentifierPrefix() + this.typeDetails.className); | ||
this.services.graph.addNode(readyClassType, this.kind.getIdentifierPrefix() + this.typeDetails.className); | ||
const nominalIdentifier = this.kind.calculateNominalIdentifier(this.typeDetails); | ||
this.services.graph.removeNode(classType, nominalIdentifier); | ||
this.services.graph.addNode(readyClassType, nominalIdentifier); | ||
} | ||
|
||
// remove the inference rules for the invalid type | ||
|
@@ -613,7 +623,7 @@ export class ClassTypeInitializer<T = unknown, T1 = unknown, T2 = unknown> exten | |
if (this.typeDetails.inferenceRuleForDeclaration === null) { | ||
// check for cycles in sub-type-relationships of classes | ||
if ((classType as ClassType).hasSubSuperClassCycles()) { | ||
throw new Error(`Circles in super-sub-class-relationships are not allowed: ${classType.getName()}`); | ||
throw new Error(`Cycles in super-sub-class-relationships are not allowed: ${classType.getName()}`); | ||
} | ||
} | ||
|
||
|
@@ -622,7 +632,7 @@ export class ClassTypeInitializer<T = unknown, T1 = unknown, T2 = unknown> exten | |
} | ||
|
||
switchedToInvalid(_previousClassType: Type): void { | ||
// do nothing | ||
// nothing specific needs to be done for Classes here, since the base implementation takes already care about all relevant stuff | ||
} | ||
|
||
override getTypeInitial(): ClassType { | ||
|
@@ -919,7 +929,7 @@ export function createNoSuperClassCyclesValidation(isRelevant: (domainElement: u | |
$problem: ValidationProblem, | ||
domainElement, | ||
severity: 'error', | ||
message: `Circles in super-sub-class-relationships are not allowed: ${classType.getName()}`, | ||
message: `Cycles in super-sub-class-relationships are not allowed: ${classType.getName()}`, | ||
}); | ||
} | ||
} | ||
|
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.
For me 'Identifiable' and 'Completed' are quite clear, but what exactly 'Invalid' means is harder to grasp, especially a going back to 'Invalid'. And then: Is the 'Invalid' after going back final (or can we get on again to Identifiable or Completed after being set back once)? Are there examples/test cases for this?
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.
Same for me, why do we need
Identifiable
? For cycle resolution you only needwaitingFor
andresolved
. Maybe it makes sense to extract this algorithm and solve in isolation.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.
'Invalid' is made explicit, since it might require less dependencies than 'Completed' and therefore speed-ups the resolution of dependencies.
I added more comments.
I guess, we can switch to Identifiable/Completed again after going to 'Invalid', but I am not yet sure.
Not yet, since they depend on the previous decision.