Skip to content

Commit

Permalink
first working version of type definitions in "wrong" order for classes
Browse files Browse the repository at this point in the history
…#22, refactorings, improved identifiers (WIP)
  • Loading branch information
JohannesMeierSE committed Nov 12, 2024
1 parent 857339a commit a2bb2ee
Show file tree
Hide file tree
Showing 19 changed files with 1,041 additions and 292 deletions.
8 changes: 5 additions & 3 deletions examples/lox/src/language/type-system/lox-type-checking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ export class LoxTypeCreator extends AbstractLangiumTypeCreator {
// class types (nominal typing):
if (isClass(node)) {
const className = node.name;
const classType = this.classKind.getOrCreateClassType({
const classType = this.classKind.createClassType({
className,
superClasses: node.superClass?.ref, // note that type inference is used here; TODO delayed
fields: node.members
Expand All @@ -245,13 +245,15 @@ export class LoxTypeCreator extends AbstractLangiumTypeCreator {
},
// inference rule for accessing fields
inferenceRuleForFieldAccess: (domainElement: unknown) => isMemberCall(domainElement) && isFieldMember(domainElement.element?.ref) && domainElement.element!.ref.$container === node
? domainElement.element!.ref.name : 'N/A', // as an alternative, use 'InferenceRuleNotApplicable' instead, what should we recommend?
? domainElement.element!.ref.name : InferenceRuleNotApplicable,
});

// TODO conversion 'nil' to classes ('TopClass')!
// any class !== all classes; here we want to say, that 'nil' is assignable to each concrete Class type!
// this.typir.conversion.markAsConvertible(typeNil, this.classKind.getOrCreateTopClassType({}), 'IMPLICIT_EXPLICIT');
this.typir.conversion.markAsConvertible(this.primitiveKind.getPrimitiveType({ primitiveName: 'nil' })!, classType, 'IMPLICIT_EXPLICIT');
classType.addListener(type => {
this.typir.conversion.markAsConvertible(this.primitiveKind.getPrimitiveType({ primitiveName: 'nil' })!, type, 'IMPLICIT_EXPLICIT');
});
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions examples/lox/test/lox-type-checking.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ describe('Test internal validation of Typir for cycles in the class inheritance

describe('LOX', () => {
// this test case will work after having the support for cyclic type definitions, since it will solve also issues with topological order of type definitions
test.todo('complete with difficult order of classes', async () => await validate(`
test.only('complete with difficult order of classes', async () => await validate(`
class SuperClass {
a: number
}
Expand All @@ -340,7 +340,7 @@ describe('LOX', () => {
var x = SubClass();
// Assigning nil to a class type
var nilTest = SubClass();
nilTest = nil;
// nilTest = nil; // TODO failed
// Accessing members of a class
var value = x.nested.method() + "wasd";
Expand Down
2 changes: 1 addition & 1 deletion packages/typir/src/features/conversion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ export class DefaultTypeConversion implements TypeConversion {
*/
const hasIntroducedCycle = this.existsEdgePath(from, from, mode);
if (hasIntroducedCycle) {
throw new Error(`Adding the conversion from ${from.identifier} to ${to.identifier} with mode ${mode} has introduced a cycle in the type graph.`);
throw new Error(`Adding the conversion from ${from.getIdentifier()} to ${to.getIdentifier()} with mode ${mode} has introduced a cycle in the type graph.`);
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/typir/src/features/equality.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ export class DefaultTypeEquality implements TypeEquality {
if (type1 === type2) {
return undefined;
}
if (type1.identifier === type2.identifier) { // this works, since identifiers are unique!
if (type1.getIdentifier() === type2.getIdentifier()) { // this works, since identifiers are unique!
return undefined;
}

Expand Down
12 changes: 8 additions & 4 deletions packages/typir/src/features/inference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ export class DefaultTypeInferenceCollector implements TypeInferenceCollector, Ty
}

addInferenceRule(rule: TypeInferenceRule, boundToType?: Type): void {
const key = boundToType?.identifier ?? '';
const key = this.getBoundToTypeKey(boundToType);
let rules = this.inferenceRules.get(key);
if (!rules) {
rules = [];
Expand All @@ -127,6 +127,10 @@ export class DefaultTypeInferenceCollector implements TypeInferenceCollector, Ty
rules.push(rule);
}

protected getBoundToTypeKey(boundToType?: Type): string {
return boundToType?.getIdentifier() ?? '';
}

inferType(domainElement: unknown): Type | InferenceProblem[] {
// is the result already in the cache?
const cached = this.cacheGet(domainElement);
Expand Down Expand Up @@ -264,11 +268,11 @@ export class DefaultTypeInferenceCollector implements TypeInferenceCollector, Ty

/* Get informed about deleted types in order to remove inference rules which are bound to them. */

addedType(_newType: Type): void {
addedType(_newType: Type, _key: string): void {
// do nothing
}
removedType(type: Type): void {
this.inferenceRules.delete(type.identifier);
removedType(type: Type, _key: string): void {
this.inferenceRules.delete(this.getBoundToTypeKey(type));
}
addedEdge(_edge: TypeEdge): void {
// do nothing
Expand Down
19 changes: 11 additions & 8 deletions packages/typir/src/features/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ export class DefaultValidationConstraints implements ValidationConstraints {
domainProperty: details.domainProperty,
domainIndex: details.domainIndex,
severity: details.severity ?? 'error',
message: details.message ?? `'${actualType.identifier}' is ${negated ? '' : 'not '}related to '${expectedType.identifier}' regarding ${strategy}.`,
message: details.message ?? `'${actualType.getIdentifier()}' is ${negated ? '' : 'not '}related to '${expectedType.getIdentifier()}' regarding ${strategy}.`,
subProblems: [comparisonResult]
}];
}
Expand All @@ -118,7 +118,7 @@ export class DefaultValidationConstraints implements ValidationConstraints {
domainProperty: details.domainProperty,
domainIndex: details.domainIndex,
severity: details.severity ?? 'error',
message: details.message ?? `'${actualType.identifier}' is ${negated ? '' : 'not '}related to '${expectedType.identifier}' regarding ${strategy}.`,
message: details.message ?? `'${actualType.getIdentifier()}' is ${negated ? '' : 'not '}related to '${expectedType.getIdentifier()}' regarding ${strategy}.`,
subProblems: [] // no sub-problems are available!
}];
} else {
Expand Down Expand Up @@ -209,7 +209,7 @@ export class DefaultValidationCollector implements ValidationCollector, TypeGrap
}

addValidationRule(rule: ValidationRule, boundToType?: Type): void {
const key = boundToType?.identifier ?? '';
const key = this.getBoundToTypeKey(boundToType);
let rules = this.validationRules.get(key);
if (!rules) {
rules = [];
Expand All @@ -219,7 +219,7 @@ export class DefaultValidationCollector implements ValidationCollector, TypeGrap
}

addValidationRuleWithBeforeAndAfter(rule: ValidationRuleWithBeforeAfter, boundToType?: Type): void {
const key = boundToType?.identifier ?? '';
const key = this.getBoundToTypeKey(boundToType);
let rules = this.validationRulesBeforeAfter.get(key);
if (!rules) {
rules = [];
Expand All @@ -228,15 +228,18 @@ export class DefaultValidationCollector implements ValidationCollector, TypeGrap
rules.push(rule);
}

protected getBoundToTypeKey(boundToType?: Type): string {
return boundToType?.getIdentifier() ?? '';
}

/* Get informed about deleted types in order to remove validation rules which are bound to them. */

addedType(_newType: Type): void {
addedType(_newType: Type, _key: string): void {
// do nothing
}
removedType(type: Type): void {
this.validationRules.delete(type.identifier);
this.validationRulesBeforeAfter.delete(type.identifier);
removedType(type: Type, _key: string): void {
this.validationRules.delete(this.getBoundToTypeKey(type));
this.validationRulesBeforeAfter.delete(this.getBoundToTypeKey(type));
}
addedEdge(_edge: TypeEdge): void {
// do nothing
Expand Down
45 changes: 26 additions & 19 deletions packages/typir/src/graph/type-graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,24 @@ export class TypeGraph {
protected readonly listeners: TypeGraphListener[] = [];

/**
* Usually this method is called by kinds after creating a a corresponding type.
* Usually this method is called by kinds after creating a corresponding type.
* Therefore it is usually not needed to call this method in an other context.
* @param type the new type
* @param key an optional key to register the type, since it is allowed to register the same type with different keys in the graph
* TODO oder stattdessen einen ProxyType verwenden? wie funktioniert das mit isClassType und isSubType? wie funktioniert removeType?
*/
addNode(type: Type): void {
const key = type.identifier;
if (this.nodes.has(key)) {
if (this.nodes.get(key) === type) {
addNode(type: Type, key?: string): void {
// TODO überprüfen, dass Identifiable-State erreicht ist??
const mapKey = key ?? type.getIdentifier();
if (this.nodes.has(mapKey)) {
if (this.nodes.get(mapKey) === type) {
// this type is already registered => that is OK
} else {
throw new Error(`Names of types must be unique: ${key}`);
throw new Error(`Names of types must be unique: ${mapKey}`);
}
} else {
this.nodes.set(key, type);
this.listeners.forEach(listener => listener.addedType(type));
this.nodes.set(mapKey, type);
this.listeners.forEach(listener => listener.addedType(type, mapKey));
}
}

Expand All @@ -48,28 +51,32 @@ export class TypeGraph {
* This is the central API call to remove a type from the type system in case that it is no longer valid/existing/needed.
* It is not required to directly inform the kind of the removed type yourself, since the kind itself will take care of removed types.
* @param type the type to remove
* @param key an optional key to register the type, since it is allowed to register the same type with different keys in the graph
*/
removeNode(type: Type): void {
const key = type.identifier;
removeNode(type: Type, key?: string): void {
const mapKey = key ?? type.getIdentifier();
// remove all edges which are connected to the type to remove
type.getAllIncomingEdges().forEach(e => this.removeEdge(e));
type.getAllOutgoingEdges().forEach(e => this.removeEdge(e));
// remove the type itself
const contained = this.nodes.delete(key);
const contained = this.nodes.delete(mapKey);
if (contained) {
this.listeners.forEach(listener => listener.removedType(type));
this.listeners.forEach(listener => listener.removedType(type, mapKey));
} else {
throw new Error(`Type does not exist: ${key}`);
throw new Error(`Type does not exist: ${mapKey}`);
}
}

getNode(name: string): Type | undefined {
return this.nodes.get(name);
getNode(key: string): Type | undefined {
return this.nodes.get(key);
}
getType(name: string): Type | undefined {
return this.getNode(name);
getType(key: string): Type | undefined {
return this.getNode(key);
}

getAllRegisteredTypes(): Type[] {
return [...this.nodes.values()];
}

addEdge(edge: TypeEdge): void {
// check constraints: no duplicated edges (same values for: from, to, $relation)
Expand Down Expand Up @@ -129,8 +136,8 @@ export class TypeGraph {
}

export interface TypeGraphListener {
addedType(type: Type): void;
removedType(type: Type): void;
addedType(type: Type, key: string): void;
removedType(type: Type, key: string): void;
addedEdge(edge: TypeEdge): void;
removedEdge(edge: TypeEdge): void;
}
Loading

0 comments on commit a2bb2ee

Please sign in to comment.