UNPKG

typir

Version:

General purpose type checking library

142 lines (123 loc) 5.46 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 { Type } from '../graph/type-node.js'; import { TypirSpecifics, TypirServices } from '../typir.js'; import { isSpecificTypirProblem, TypirProblem } from '../utils/utils-definitions.js'; import { EdgeCachingInformation, TypeRelationshipCaching } from './caching.js'; import { TypeEdge, isTypeEdge } from '../graph/type-edge.js'; import { assertUnreachable } from '../utils/utils.js'; export interface TypeEqualityProblem extends TypirProblem { $problem: 'TypeEqualityProblem'; type1: Type; type2: Type; subProblems: TypirProblem[]; // might be empty } export const TypeEqualityProblem = 'TypeEqualityProblem'; export function isTypeEqualityProblem(problem: unknown): problem is TypeEqualityProblem { return isSpecificTypirProblem(problem, TypeEqualityProblem); } /** * Analyzes, whether there is an equality-relationship between two types. * * In contrast to type comparisons with type1 === type2 or type1.identifier === type2.identifier, * equality will take alias types and so on into account as well. */ export interface TypeEquality { areTypesEqual(type1: Type, type2: Type): boolean; getTypeEqualityProblem(type1: Type, type2: Type): TypeEqualityProblem | undefined; } export class DefaultTypeEquality<Specifics extends TypirSpecifics> implements TypeEquality { protected readonly typeRelationships: TypeRelationshipCaching; constructor(services: TypirServices<Specifics>) { this.typeRelationships = services.caching.TypeRelationships; } areTypesEqual(type1: Type, type2: Type): boolean { return this.getTypeEqualityProblem(type1, type2) === undefined; } getTypeEqualityProblem(type1: Type, type2: Type): TypeEqualityProblem | undefined { const cache: TypeRelationshipCaching = this.typeRelationships; const linkData = cache.getRelationshipBidirectional<EqualityEdge>(type1, type2, EqualityEdge); const equalityCaching = linkData?.cachingInformation ?? 'UNKNOWN'; function save(equalityCaching: EdgeCachingInformation, error: TypeEqualityProblem | undefined): void { const newEdge: EqualityEdge = { $relation: EqualityEdge, from: type1, to: type2, cachingInformation: 'LINK_EXISTS', error, }; cache.setOrUpdateBidirectionalRelationship(newEdge, equalityCaching); } // skip recursive checking if (equalityCaching === 'PENDING') { /** 'undefined' should be correct here ... * - since this relationship will be checked earlier/higher/upper in the call stack again * - since this values is not cached and therefore NOT reused in the earlier call! */ return undefined; } // the result is already known if (equalityCaching === 'LINK_EXISTS') { return undefined; } if (equalityCaching === 'NO_LINK') { return { $problem: TypeEqualityProblem, type1, type2, subProblems: linkData?.error ? [linkData.error] : [], }; } // do the expensive calculation now if (equalityCaching === 'UNKNOWN') { // mark the current relationship as PENDING to detect and resolve cycling checks save('PENDING', undefined); // do the actual calculation const result = this.calculateEquality(type1, type2); // this allows to cache results (and to re-set the PENDING state) if (result === undefined) { save('LINK_EXISTS', undefined); } else { save('NO_LINK', result); } return result; } assertUnreachable(equalityCaching); } protected calculateEquality(type1: Type, type2: Type): TypeEqualityProblem | undefined { if (type1 === type2) { return undefined; } if (type1.getIdentifier() === type2.getIdentifier()) { // this works, since identifiers are unique! return undefined; } // use the type-specific logic // ask the 1st type const result1 = type1.analyzeTypeEqualityProblems(type2); if (result1.length <= 0) { return undefined; } // ask the 2nd type const result2 = type2.analyzeTypeEqualityProblems(type1); if (result2.length <= 0) { return undefined; } // both types reported, that they are diffferent return { $problem: TypeEqualityProblem, type1, type2, subProblems: [...result1, ...result2] // return the equality problems of both types }; } } export interface EqualityEdge extends TypeEdge { readonly $relation: 'EqualityEdge'; readonly error: TypeEqualityProblem | undefined; } export const EqualityEdge = 'EqualityEdge'; export function isEqualityEdge(edge: unknown): edge is EqualityEdge { return isTypeEdge(edge) && edge.$relation === EqualityEdge; }