UNPKG

typir

Version:

General purpose type checking library

110 lines 4.47 kB
/****************************************************************************** * 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