Skip to content

Commit

Permalink
Merge pull request #553 from jvalue/import-cycle-evaluate-expression
Browse files Browse the repository at this point in the history
Remove Import Cycle in type-inference.ts
  • Loading branch information
georg-schwarz authored Apr 18, 2024
2 parents 5b9b999 + 9ae2670 commit 2326441
Show file tree
Hide file tree
Showing 12 changed files with 180 additions and 161 deletions.
1 change: 0 additions & 1 deletion libs/language-server/src/lib/ast/expressions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ export * from './evaluation-strategy';
export * from './evaluation-context';
export * from './internal-value-representation';
export * from './operator-registry';
// eslint-disable-next-line import/no-cycle
export * from './type-inference';
export * from './typeguards';
export * from './evaluate-expression';
91 changes: 5 additions & 86 deletions libs/language-server/src/lib/ast/expressions/type-inference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
//
// SPDX-License-Identifier: AGPL-3.0-only

import { strict as assert } from 'assert';

import { assertUnreachable } from 'langium';

import { type ValidationContext } from '../../validation/validation-context';
Expand Down Expand Up @@ -36,16 +34,16 @@ import {
isValuetypeAssignmentLiteral,
} from '../generated/ast';
import { getNextAstNodeContainer } from '../model-util';
// eslint-disable-next-line import/no-cycle
import {
type AtomicValueType,
type PrimitiveValueType,
type ValueType,
type ValueTypeProvider,
type WrapperFactoryProvider,
isAtomicValueType,
isPrimitiveValueType,
} from '../wrappers';
import {
getValuetypeHierarchyStack,
pickCommonAtomicValueType,
pickCommonPrimitiveValuetype,
} from '../wrappers/util/value-type-util';

import { isEveryValueDefined } from './typeguards';

Expand Down Expand Up @@ -289,85 +287,6 @@ function inferCollectionElementTypes(
return elementValuetypes;
}

type ValuetypeHierarchyStack = [PrimitiveValueType, ...AtomicValueType[]];

function getValuetypeHierarchyStack(
valueType: ValueType,
): ValuetypeHierarchyStack {
if (isPrimitiveValueType(valueType)) {
return [valueType];
} else if (isAtomicValueType(valueType)) {
const supertype = valueType.getSupertype();
assert(supertype !== undefined);
return [...getValuetypeHierarchyStack(supertype), valueType];
}
throw new Error(
'Should be unreachable, encountered an unknown kind of value type',
);
}

function pickCommonPrimitiveValuetype(
primitiveValuetypes: PrimitiveValueType[],
): PrimitiveValueType | undefined {
assert(primitiveValuetypes.length > 0);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
let resultingType: PrimitiveValueType = primitiveValuetypes[0]!;
for (let i = 1; i < primitiveValuetypes.length; ++i) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const currentType = primitiveValuetypes[i]!;

if (currentType.isConvertibleTo(resultingType)) {
continue;
}

if (resultingType.isConvertibleTo(currentType)) {
// Pick the more general type as a result
resultingType = currentType;
continue;
}

// Unable to convert the value types into each other, so there is no common primitive value type
return undefined;
}
return resultingType;
}

function pickCommonAtomicValueType(
stacks: ValuetypeHierarchyStack[],
): PrimitiveValueType | AtomicValueType | undefined {
const minimumStackLength = Math.min(...stacks.map((stack) => stack.length));

let resultingType: PrimitiveValueType | AtomicValueType | undefined =
undefined;
for (let stackLevel = 1; stackLevel < minimumStackLength; ++stackLevel) {
const typesOfCurrentLevel: (PrimitiveValueType | AtomicValueType)[] =
stacks.map(
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
(stack) => stack[stackLevel]!,
);

if (!areAllTypesEqual(typesOfCurrentLevel)) {
// Return the common value type of the previous level
return resultingType;
}

// Pick any type of the current level since they are all equal
resultingType = typesOfCurrentLevel[0];
}
return resultingType;
}

function areAllTypesEqual(types: ValueType[]): boolean {
for (let i = 1; i < types.length; i++) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
if (!types[i - 1]!.equals(types[i]!)) {
return false;
}
}

return true;
}

function inferTypeFromValueKeyword(
expression: ValueKeywordLiteral,
validationContext: ValidationContext,
Expand Down
14 changes: 9 additions & 5 deletions libs/language-server/src/lib/ast/wrappers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
//
// SPDX-License-Identifier: AGPL-3.0-only

export * from './wrapper-factory-provider';
export * from './util';

/*
* Note: Only export types if possible to enforce usage of WrapperFactory outside this directory.
* This allows us to avoid dependency cycles between the language server and interpreter.
Expand All @@ -17,13 +20,14 @@ export {
export { type PipeWrapper } from './pipe-wrapper';
export { type PipelineWrapper } from './pipeline-wrapper';

// eslint-disable-next-line import/no-cycle
export { type BlockTypeWrapper } from './typed-object/block-type-wrapper';
export { type CompositeBlockTypeWrapper } from './typed-object/composite-block-type-wrapper';
export { type ConstraintTypeWrapper } from './typed-object/constrainttype-wrapper';
export * from './typed-object/typed-object-wrapper';
export {
ExampleDoc,
PropertyDocs,
PropertySpecification,
type TypedObjectWrapper,
} from './typed-object/typed-object-wrapper';

export * from './value-type'; // type export handled one level deeper

export * from './util';
export * from './wrapper-factory-provider';
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { strict as assert } from 'assert';
import { type Reference, isReference } from 'langium';

import { RuntimeParameterProvider } from '../../../services';
// eslint-disable-next-line import/no-cycle
import {
EvaluationContext,
type OperatorEvaluatorRegistry,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { type CompositeBlockTypeDefinition } from '../../generated/ast';
import { type ValueTypeProvider } from '../value-type';
import { type WrapperFactoryProvider } from '../wrapper-factory-provider';

// eslint-disable-next-line import/no-cycle
import { BlockTypeWrapper } from './block-type-wrapper';

export class CompositeBlockTypeWrapper extends BlockTypeWrapper {
Expand Down
1 change: 1 addition & 0 deletions libs/language-server/src/lib/ast/wrappers/util/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
export * from './column-id-util';
export * from './cell-index';
export * from './cell-range-util';
export * from './value-type-util';
97 changes: 97 additions & 0 deletions libs/language-server/src/lib/ast/wrappers/util/value-type-util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg
//
// SPDX-License-Identifier: AGPL-3.0-only

import { strict as assert } from 'assert';

import {
type AtomicValueType,
isAtomicValueType,
} from '../value-type/atomic-value-type';
import {
type PrimitiveValueType,
isPrimitiveValueType,
} from '../value-type/primitive';
import { type ValueType } from '../value-type/value-type';

type ValuetypeHierarchyStack = [PrimitiveValueType, ...AtomicValueType[]];

export function getValuetypeHierarchyStack(
valueType: ValueType,
): ValuetypeHierarchyStack {
if (isPrimitiveValueType(valueType)) {
return [valueType];
} else if (isAtomicValueType(valueType)) {
const supertype = valueType.getSupertype();
assert(supertype !== undefined);
return [...getValuetypeHierarchyStack(supertype), valueType];
}
throw new Error(
'Should be unreachable, encountered an unknown kind of value type',
);
}

export function pickCommonPrimitiveValuetype(
primitiveValuetypes: PrimitiveValueType[],
): PrimitiveValueType | undefined {
assert(primitiveValuetypes.length > 0);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
let resultingType: PrimitiveValueType = primitiveValuetypes[0]!;
for (let i = 1; i < primitiveValuetypes.length; ++i) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const currentType = primitiveValuetypes[i]!;

if (currentType.isConvertibleTo(resultingType)) {
continue;
}

if (resultingType.isConvertibleTo(currentType)) {
// Pick the more general type as a result
resultingType = currentType;
continue;
}

// Unable to convert the value types into each other, so there is no common primitive value type
return undefined;
}
return resultingType;
}

export function pickCommonAtomicValueType(
stacks: ValuetypeHierarchyStack[],
): PrimitiveValueType | AtomicValueType | undefined {
const minimumStackLength = Math.min(...stacks.map((stack) => stack.length));

let resultingType: PrimitiveValueType | AtomicValueType | undefined =
undefined;
for (let stackLevel = 1; stackLevel < minimumStackLength; ++stackLevel) {
const typesOfCurrentLevel: (PrimitiveValueType | AtomicValueType)[] =
stacks.map(
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
(stack) => stack[stackLevel]!,
);

if (!areAllTypesEqual(typesOfCurrentLevel)) {
// Return the common value type of the previous level
return resultingType;
}

// Pick any type of the current level since they are all equal
resultingType = typesOfCurrentLevel[0];
}
return resultingType;
}

export function areAllTypesEqual(types: ValueType[]): boolean {
for (let i = 1; i < types.length; i++) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const current = types[i - 1]!;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const afterCurrent = types[i]!;
if (!current.equals(afterCurrent)) {
return false;
}
}

return true;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg
//
// SPDX-License-Identifier: AGPL-3.0-only

import { type InternalValueRepresentation } from '../../expressions';

import { type ValueType, type ValueTypeVisitor } from './value-type';

export abstract class AbstractValueType<I extends InternalValueRepresentation>
implements ValueType<I>
{
abstract acceptVisitor<R>(visitor: ValueTypeVisitor<R>): R;

isSubtypeOf(other: ValueType): boolean {
let othersSupertype = other.getSupertype();
while (othersSupertype !== undefined) {
if (othersSupertype === this) {
return true;
}
othersSupertype = othersSupertype.getSupertype();
}
return false;
}

getSupertype(): ValueType | undefined {
if (this.hasSupertypeCycle()) {
return undefined;
}
return this.doGetSupertype();
}

protected abstract doGetSupertype(): ValueType | undefined;

abstract equals(target: ValueType): boolean;

abstract isAllowedAsRuntimeParameter(): boolean;

abstract isConvertibleTo(target: ValueType): boolean;

isReferenceableByUser(): boolean {
return false;
}

abstract isInternalValueRepresentation(
operandValue: InternalValueRepresentation | undefined,
): operandValue is I;

abstract getName(): string;

hasSupertypeCycle(visited: ValueType[] = []): boolean {
const cycleDetected = visited.some((v) => v.equals(this));
if (cycleDetected) {
return true;
}
visited.push(this);

const supertype = this.doGetSupertype();
if (supertype === undefined) {
return false;
}

return supertype.hasSupertypeCycle(visited);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,10 @@ import {
import { type AstNodeWrapper } from '../ast-node-wrapper';
import { type WrapperFactoryProvider } from '../wrapper-factory-provider';

import { AbstractValueType } from './abstract-value-type';
import { type ValueTypeProvider } from './primitive';
import { CollectionValueType } from './primitive/collection/collection-value-type';
import {
AbstractValueType,
type ValueType,
type ValueTypeVisitor,
} from './value-type';
import { type ValueType, type ValueTypeVisitor } from './value-type';

export class AtomicValueType
extends AbstractValueType<InternalValueRepresentation>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
// SPDX-License-Identifier: AGPL-3.0-only

import { type InternalValueRepresentation } from '../../../expressions/internal-value-representation';
import { AbstractValueType, type ValueType } from '../value-type';
import { AbstractValueType } from '../abstract-value-type';
import { type ValueType } from '../value-type';

export abstract class PrimitiveValueType<
I extends InternalValueRepresentation = InternalValueRepresentation,
Expand Down
Loading

0 comments on commit 2326441

Please sign in to comment.