typir
Version:
General purpose type checking library
110 lines • 4.47 kB
JavaScript
/******************************************************************************
* Copyright 2024 TypeFox GmbH
* This program and the accompanying materials are made available under the
* terms of the MIT License, which is available in the project root.
******************************************************************************/
import { isTypeEdge } from '../graph/type-edge.js';
/**
* Design decisions:
* - Do not store transitive relationships, since they must be removed, when types of the corresponding path are removed!
* - Store only EXPLICIT and IMPLICIT relationships, since this is not required, missing edges means NONE/SELF.
*/
export class DefaultTypeConversion {
constructor(services) {
this.equality = services.Equality;
this.graph = services.infrastructure.Graph;
this.algorithms = services.infrastructure.GraphAlgorithms;
}
markAsConvertible(from, to, mode) {
let edge = this.getConversionEdge(from, to);
if (!edge) {
// create a missing edge (with the desired mode)
edge = {
$relation: ConversionEdge,
from,
to,
cachingInformation: 'LINK_EXISTS',
mode,
};
this.graph.addEdge(edge);
}
else {
// update the mode
edge.mode = mode;
}
if (mode === 'IMPLICIT_EXPLICIT') {
/* check that the new edges did not introduce cycles
* if it did, the from node will be reachable via a cycle path
*/
const hasIntroducedCycle = this.existsEdgePath(from, from, mode);
if (hasIntroducedCycle) {
throw new Error(`Adding the conversion from ${from.getIdentifier()} to ${to.getIdentifier()} with mode ${mode} has introduced a cycle in the type graph.`);
}
}
}
isTransitive(mode) {
// by default, only IMPLICIT is transitive!
return mode === 'IMPLICIT_EXPLICIT';
}
getConversion(from, to) {
// check whether the direct conversion is stored in the type graph (this is quite fast)
const edge = this.getConversionEdge(from, to);
if (edge) {
return edge.mode;
}
// special case: if both types are equal, no conversion is needed (often this check is quite fast)
if (this.equality.areTypesEqual(from, to)) {
return 'SELF';
}
// check whether there is a transitive relationship (in general, these checks are expensive)
if (this.isTransitive('EXPLICIT') && this.isTransitivelyConvertable(from, to, 'EXPLICIT')) {
return 'EXPLICIT';
}
if (this.isTransitive('IMPLICIT_EXPLICIT') && this.isTransitivelyConvertable(from, to, 'IMPLICIT_EXPLICIT')) {
return 'IMPLICIT_EXPLICIT';
}
// the default case
return 'NONE';
}
collectReachableTypes(from, mode) {
return this.algorithms.collectReachableTypes(from, [ConversionEdge], edge => edge.mode === mode);
}
existsEdgePath(from, to, mode) {
return this.algorithms.existsEdgePath(from, to, [ConversionEdge], edge => edge.mode === mode);
}
isTransitivelyConvertable(from, to, mode) {
if (from === to) {
return true;
}
else {
return (this.existsEdgePath(from, to, mode));
}
}
isImplicitExplicitConvertible(from, to) {
return this.getConversion(from, to) === 'IMPLICIT_EXPLICIT';
}
isExplicitConvertible(from, to) {
return this.getConversion(from, to) === 'EXPLICIT';
}
isNoneConvertible(from, to) {
return this.getConversion(from, to) === 'NONE';
}
isSelfConvertible(from, to) {
return this.getConversion(from, to) === 'SELF';
}
isConvertible(from, to) {
const currentMode = this.getConversion(from, to);
return currentMode === 'IMPLICIT_EXPLICIT' || currentMode === 'EXPLICIT' || currentMode === 'SELF';
}
getConversionEdge(from, to) {
return from.getOutgoingEdges(ConversionEdge).find(edge => edge.to === to);
}
getConvertibleTo(from, mode) {
return this.collectReachableTypes(from, mode);
}
}
export const ConversionEdge = 'ConversionEdge';
export function isConversionEdge(edge) {
return isTypeEdge(edge) && edge.$relation === ConversionEdge;
}
//# sourceMappingURL=conversion.js.map