Skip to content

Commit

Permalink
Merge pull request #307 from jvalue/refactor-type-inference
Browse files Browse the repository at this point in the history
Introduce type computers and implicit conversion
  • Loading branch information
felix-oq authored May 2, 2023
2 parents da0b46e + 827ae9f commit df0deb5
Show file tree
Hide file tree
Showing 23 changed files with 394 additions and 473 deletions.
88 changes: 39 additions & 49 deletions libs/language-server/src/lib/ast/expressions/operator-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,9 @@

/* eslint-disable import/no-cycle */

import { ValidationContext } from '../../validation/validation-context';
import { BinaryExpression, UnaryExpression } from '../generated/ast';
import {
BinaryExpressionOperator,
PropertyValuetype,
UnaryExpressionOperator,
} from '../model-util';

Expand Down Expand Up @@ -36,31 +34,23 @@ import { SqrtOperatorEvaluator } from './evaluators/sqrt-operator-evaluator';
import { SubtractionOperatorEvaluator } from './evaluators/subtraction-operator-evaluator';
import { XorOperatorEvaluator } from './evaluators/xor-operator-evaluator';
import { OperatorEvaluator } from './operator-evaluator';
import { inferBinaryArithmeticExpressionType } from './operators/binary-arithmetic-expression';
import { inferBinaryEqualityExpressionType } from './operators/binary-equality-expression';
import { inferBinaryExponentialExpressionType } from './operators/binary-exponential-expression';
import { inferBinaryLogicalExpressionType } from './operators/binary-logical-expression';
import { inferBinaryRelationalExpressionType } from './operators/binary-relational-expression';
import { inferUnaryIntegerConversionExpressionType } from './operators/unary-integer-conversion-expression';
import { inferUnaryNotExpressionType } from './operators/unary-not-expression';
import { inferUnarySignExpressionType } from './operators/unary-sign-expression';
import { inferUnarySqrtExpressionType } from './operators/unary-sqrt-expression';

export type UnaryTypeInferenceFunction = (
innerType: PropertyValuetype,
expression: UnaryExpression,
context: ValidationContext | undefined,
) => PropertyValuetype | undefined;

export type BinaryTypeInferenceFunction = (
leftType: PropertyValuetype,
rightType: PropertyValuetype,
expression: BinaryExpression,
context: ValidationContext | undefined,
) => PropertyValuetype | undefined;
import {
BinaryOperatorTypeComputer,
UnaryOperatorTypeComputer,
} from './operator-type-computer';
import { BasicArithmeticOperatorTypeComputer } from './type-computers/basic-arithmetic-operator-type-computer';
import { DivisionOperatorTypeComputer } from './type-computers/division-operator-type-computer';
import { EqualityOperatorTypeComputer } from './type-computers/equality-operator-type-computer';
import { ExponentialOperatorTypeComputer } from './type-computers/exponential-operator-type-computer';
import { IntegerConversionOperatorTypeComputer } from './type-computers/integer-conversion-operator-type-computer';
import { LogicalOperatorTypeComputer } from './type-computers/logical-operator-type-computer';
import { NotOperatorTypeComputer } from './type-computers/not-operator-type-computer';
import { RelationalOperatorTypeComputer } from './type-computers/relational-operator-type-computer';
import { SignOperatorTypeComputer } from './type-computers/sign-operator-type-computer';
import { SqrtOperatorTypeComputer } from './type-computers/sqrt-operator-type-computer';

export interface UnaryOperatorEntry {
typeInference: UnaryTypeInferenceFunction;
typeInference: UnaryOperatorTypeComputer;
evaluation: OperatorEvaluator<UnaryExpression>;
}

Expand All @@ -69,37 +59,37 @@ export const unaryOperatorRegistry: Record<
UnaryOperatorEntry
> = {
not: {
typeInference: inferUnaryNotExpressionType,
typeInference: new NotOperatorTypeComputer(),
evaluation: new NotOperatorEvaluator(),
},
'+': {
typeInference: inferUnarySignExpressionType,
typeInference: new SignOperatorTypeComputer(),
evaluation: new PlusOperatorEvaluator(),
},
'-': {
typeInference: inferUnarySignExpressionType,
typeInference: new SignOperatorTypeComputer(),
evaluation: new MinusOperatorEvaluator(),
},
sqrt: {
typeInference: inferUnarySqrtExpressionType,
typeInference: new SqrtOperatorTypeComputer(),
evaluation: new SqrtOperatorEvaluator(),
},
floor: {
typeInference: inferUnaryIntegerConversionExpressionType,
typeInference: new IntegerConversionOperatorTypeComputer(),
evaluation: new FloorOperatorEvaluator(),
},
ceil: {
typeInference: inferUnaryIntegerConversionExpressionType,
typeInference: new IntegerConversionOperatorTypeComputer(),
evaluation: new CeilOperatorEvaluator(),
},
round: {
typeInference: inferUnaryIntegerConversionExpressionType,
typeInference: new IntegerConversionOperatorTypeComputer(),
evaluation: new RoundOperatorEvaluator(),
},
};

export interface BinaryOperatorEntry {
typeInference: BinaryTypeInferenceFunction;
typeInference: BinaryOperatorTypeComputer;
evaluation: OperatorEvaluator<BinaryExpression>;
}

Expand All @@ -108,67 +98,67 @@ export const binaryOperatorRegistry: Record<
BinaryOperatorEntry
> = {
pow: {
typeInference: inferBinaryExponentialExpressionType,
typeInference: new ExponentialOperatorTypeComputer(),
evaluation: new PowOperatorEvaluator(),
},
root: {
typeInference: inferBinaryExponentialExpressionType,
typeInference: new ExponentialOperatorTypeComputer(),
evaluation: new RootOperatorEvaluator(),
},
'*': {
typeInference: inferBinaryArithmeticExpressionType,
typeInference: new BasicArithmeticOperatorTypeComputer(),
evaluation: new MultiplicationOperatorEvaluator(),
},
'/': {
typeInference: inferBinaryArithmeticExpressionType,
typeInference: new DivisionOperatorTypeComputer(),
evaluation: new DivisionOperatorEvaluator(),
},
'%': {
typeInference: inferBinaryArithmeticExpressionType,
typeInference: new DivisionOperatorTypeComputer(),
evaluation: new ModuloOperatorEvaluator(),
},
'+': {
typeInference: inferBinaryArithmeticExpressionType,
typeInference: new BasicArithmeticOperatorTypeComputer(),
evaluation: new AdditionOperatorEvaluator(),
},
'-': {
typeInference: inferBinaryArithmeticExpressionType,
typeInference: new BasicArithmeticOperatorTypeComputer(),
evaluation: new SubtractionOperatorEvaluator(),
},
'<': {
typeInference: inferBinaryRelationalExpressionType,
typeInference: new RelationalOperatorTypeComputer(),
evaluation: new LessThanOperatorEvaluator(),
},
'<=': {
typeInference: inferBinaryRelationalExpressionType,
typeInference: new RelationalOperatorTypeComputer(),
evaluation: new LessEqualOperatorEvaluator(),
},
'>': {
typeInference: inferBinaryRelationalExpressionType,
typeInference: new RelationalOperatorTypeComputer(),
evaluation: new GreaterThanOperatorEvaluator(),
},
'>=': {
typeInference: inferBinaryRelationalExpressionType,
typeInference: new RelationalOperatorTypeComputer(),
evaluation: new GreaterEqualOperatorEvaluator(),
},
'==': {
typeInference: inferBinaryEqualityExpressionType,
typeInference: new EqualityOperatorTypeComputer(),
evaluation: new EqualityOperatorEvaluator(),
},
'!=': {
typeInference: inferBinaryEqualityExpressionType,
typeInference: new EqualityOperatorTypeComputer(),
evaluation: new InequalityOperatorEvaluator(),
},
xor: {
typeInference: inferBinaryLogicalExpressionType,
typeInference: new LogicalOperatorTypeComputer(),
evaluation: new XorOperatorEvaluator(),
},
and: {
typeInference: inferBinaryLogicalExpressionType,
typeInference: new LogicalOperatorTypeComputer(),
evaluation: new AndOperatorEvaluator(),
},
or: {
typeInference: inferBinaryLogicalExpressionType,
typeInference: new LogicalOperatorTypeComputer(),
evaluation: new OrOperatorEvaluator(),
},
};
146 changes: 146 additions & 0 deletions libs/language-server/src/lib/ast/expressions/operator-type-computer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg
//
// SPDX-License-Identifier: AGPL-3.0-only

import { ValidationContext } from '../../validation/validation-context';
import { BinaryExpression, UnaryExpression } from '../generated/ast';
// eslint-disable-next-line import/no-cycle
import { PropertyValuetype } from '../model-util';

export interface UnaryOperatorTypeComputer {
/**
* Computes the type of a unary operator by the type of its operand.
* @param operandType the type of the operand
* @param expression the expression to use for diagnostics
* @param context the validation context to use for diagnostics
* @returns the resulting type of the operator or `undefined` if the type could not be inferred
*/
computeType(
operandType: PropertyValuetype,
expression: UnaryExpression,
context: ValidationContext | undefined,
): PropertyValuetype | undefined;
}

export abstract class DefaultUnaryOperatorTypeComputer
implements UnaryOperatorTypeComputer
{
constructor(protected readonly expectedOperandType: PropertyValuetype) {}

computeType(
operandType: PropertyValuetype,
expression: UnaryExpression,
context: ValidationContext | undefined,
): PropertyValuetype | undefined {
if (!convertsImplicitlyTo(operandType, this.expectedOperandType)) {
context?.accept(
'error',
generateUnexpectedTypeMessage(this.expectedOperandType, operandType),
{
node: expression.expression,
},
);
return undefined;
}
return this.doComputeType(operandType);
}

protected abstract doComputeType(
operandType: PropertyValuetype,
): PropertyValuetype;
}

export interface BinaryOperatorTypeComputer {
/**
* Computes the type of a binary operator by the type of its operands.
* @param leftType the type of the left operand
* @param rightType the type of the right operand
* @param expression the expression to use for diagnostics
* @param context the validation context to use for diagnostics
* @returns the resulting type of the operator or `undefined` if the type could not be inferred
*/
computeType(
leftType: PropertyValuetype,
rightType: PropertyValuetype,
expression: BinaryExpression,
context: ValidationContext | undefined,
): PropertyValuetype | undefined;
}

export abstract class DefaultBinaryOperatorTypeComputer
implements BinaryOperatorTypeComputer
{
constructor(
protected readonly expectedLeftOperandType: PropertyValuetype,
protected readonly expectedRightOperandType: PropertyValuetype,
) {}

computeType(
leftOperandType: PropertyValuetype,
rightOperandType: PropertyValuetype,
expression: BinaryExpression,
context: ValidationContext | undefined,
): PropertyValuetype | undefined {
let typeErrorOccurred = false;

if (!convertsImplicitlyTo(leftOperandType, this.expectedLeftOperandType)) {
context?.accept(
'error',
generateUnexpectedTypeMessage(
this.expectedLeftOperandType,
leftOperandType,
),
{
node: expression.left,
},
);
typeErrorOccurred = true;
}

if (
!convertsImplicitlyTo(rightOperandType, this.expectedRightOperandType)
) {
context?.accept(
'error',
generateUnexpectedTypeMessage(
this.expectedRightOperandType,
rightOperandType,
),
{
node: expression.right,
},
);
typeErrorOccurred = true;
}

if (typeErrorOccurred) {
return undefined;
}

return this.doComputeType(leftOperandType, rightOperandType);
}

protected abstract doComputeType(
leftOperandType: PropertyValuetype,
rightOperandType: PropertyValuetype,
): PropertyValuetype;
}

export function convertsImplicitlyTo(
from: PropertyValuetype,
to: PropertyValuetype,
) {
return (
from === to ||
(from === PropertyValuetype.INTEGER && to === PropertyValuetype.DECIMAL)
);
}

function generateUnexpectedTypeMessage(
expectedTypes: PropertyValuetype | PropertyValuetype[],
actualType: PropertyValuetype,
) {
return `The operand needs to be of type ${
Array.isArray(expectedTypes) ? expectedTypes.join(' or ') : expectedTypes
} but is of type ${actualType}`;
}

This file was deleted.

Loading

0 comments on commit df0deb5

Please sign in to comment.