Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow use of operators for cells #1247

Merged
merged 9 commits into from
Oct 31, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { SafeDsModuleMembers } from './safe-ds-module-members.js';
import { resourceNameToUri } from '../../helpers/resources.js';
import { URI } from 'langium';

const CELL_URI = resourceNameToUri('builtins/safeds/data/tabular/containers/Cell.sdsstub');
const CORE_CLASSES_URI = resourceNameToUri('builtins/safeds/lang/coreClasses.sdsstub');
const IMAGE_URI = resourceNameToUri('builtins/safeds/data/image/containers/Image.sdsstub');
const TABLE_URI = resourceNameToUri('builtins/safeds/data/tabular/containers/Table.sdsstub');
Expand All @@ -16,6 +17,10 @@ export class SafeDsClasses extends SafeDsModuleMembers<SdsClass> {
return this.getClass('Boolean');
}

get Cell(): SdsClass | undefined {
return this.getClass('Cell', CELL_URI);
}

get Float(): SdsClass | undefined {
return this.getClass('Float');
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ import {
} from './utilityFunctions.js';
import { CODEGEN_PREFIX } from './constants.js';
import { SafeDsSlicer } from '../../flow/safe-ds-slicer.js';
import { SafeDsTypeChecker } from '../../typing/safe-ds-type-checker.js';
import { SafeDsCoreTypes } from '../../typing/safe-ds-core-types.js';

const LAMBDA_PREFIX = `${CODEGEN_PREFIX}lambda_`;
const BLOCK_LAMBDA_RESULT_PREFIX = `${CODEGEN_PREFIX}block_lambda_result_`;
Expand All @@ -130,18 +132,22 @@ const SPACING = new CompositeGeneratorNode(NL, NL);

export class SafeDsPythonGenerator {
private readonly builtinAnnotations: SafeDsAnnotations;
private readonly coreTypes: SafeDsCoreTypes;
private readonly nodeMapper: SafeDsNodeMapper;
private readonly partialEvaluator: SafeDsPartialEvaluator;
private readonly purityComputer: SafeDsPurityComputer;
private readonly slicer: SafeDsSlicer;
private readonly typeChecker: SafeDsTypeChecker;
private readonly typeComputer: SafeDsTypeComputer;

constructor(services: SafeDsServices) {
this.builtinAnnotations = services.builtins.Annotations;
this.coreTypes = services.typing.CoreTypes;
this.nodeMapper = services.helpers.NodeMapper;
this.partialEvaluator = services.evaluation.PartialEvaluator;
this.purityComputer = services.purity.PurityComputer;
this.slicer = services.flow.Slicer;
this.typeChecker = services.typing.TypeChecker;
this.typeComputer = services.typing.TypeComputer;
}

Expand Down Expand Up @@ -722,19 +728,43 @@ export class SafeDsPythonGenerator {
} else if (isSdsInfixOperation(expression)) {
const leftOperand = this.generateExpression(expression.leftOperand, frame);
const rightOperand = this.generateExpression(expression.rightOperand, frame);

const leftOperandType = this.typeComputer.computeType(expression.leftOperand);
const rightOperandType = this.typeComputer.computeType(expression.rightOperand);

switch (expression.operator) {
case 'or':
frame.addUtility(eagerOr);
return expandTracedToNode(expression)`${traceToNode(
expression,
'operator',
)(eagerOr.name)}(${leftOperand}, ${rightOperand})`;
if (
this.typeChecker.isSubtypeOf(leftOperandType, this.coreTypes.Boolean) &&
this.typeChecker.isSubtypeOf(rightOperandType, this.coreTypes.Boolean)
) {
frame.addUtility(eagerOr);
return expandTracedToNode(expression)`${traceToNode(
expression,
'operator',
)(eagerOr.name)}(${leftOperand}, ${rightOperand})`;
} else {
return expandTracedToNode(expression)`(${leftOperand}) ${traceToNode(
expression,
'operator',
)('|')} (${rightOperand})`;
}
case 'and':
frame.addUtility(eagerAnd);
return expandTracedToNode(expression)`${traceToNode(
expression,
'operator',
)(eagerAnd.name)}(${leftOperand}, ${rightOperand})`;
if (
this.typeChecker.isSubtypeOf(leftOperandType, this.coreTypes.Boolean) &&
this.typeChecker.isSubtypeOf(rightOperandType, this.coreTypes.Boolean)
) {
frame.addUtility(eagerAnd);
return expandTracedToNode(expression)`${traceToNode(
expression,
'operator',
)(eagerAnd.name)}(${leftOperand}, ${rightOperand})`;
} else {
return expandTracedToNode(expression)`(${leftOperand}) ${traceToNode(
expression,
'operator',
)('&')} (${rightOperand})`;
}
case '?:':
frame.addUtility(eagerElvis);
return expandTracedToNode(expression)`${traceToNode(
Expand Down Expand Up @@ -807,7 +837,14 @@ export class SafeDsPythonGenerator {
const operand = this.generateExpression(expression.operand, frame);
switch (expression.operator) {
case 'not':
return expandTracedToNode(expression)`${traceToNode(expression, 'operator')('not')} (${operand})`;
const operandType = this.typeComputer.computeType(expression.operand);
if (this.typeChecker.isSubtypeOf(operandType, this.coreTypes.Boolean)) {
return expandTracedToNode(
expression,
)`${traceToNode(expression, 'operator')('not')} (${operand})`;
} else {
return expandTracedToNode(expression)`${traceToNode(expression, 'operator')('~')}(${operand})`;
}
case '-':
return expandTracedToNode(expression)`${traceToNode(expression, 'operator')('-')}(${operand})`;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,16 +77,22 @@ import {
substitutionsAreEqual,
UnknownEvaluatedNode,
} from './model.js';
import type { SafeDsTypeComputer } from '../typing/safe-ds-type-computer.js';
import { SafeDsCoreTypes } from '../typing/safe-ds-core-types.js';

export class SafeDsPartialEvaluator {
private readonly astNodeLocator: AstNodeLocator;
private readonly coreTypes: SafeDsCoreTypes;
private readonly nodeMapper: SafeDsNodeMapper;
private readonly typeComputer: () => SafeDsTypeComputer;

private readonly cache: WorkspaceCache<string, EvaluatedNode>;

constructor(services: SafeDsServices) {
this.astNodeLocator = services.workspace.AstNodeLocator;
this.coreTypes = services.typing.CoreTypes;
this.nodeMapper = services.helpers.NodeMapper;
this.typeComputer = () => services.typing.TypeComputer;

this.cache = new WorkspaceCache(services.shared);
}
Expand Down Expand Up @@ -357,7 +363,10 @@ export class SafeDsPartialEvaluator {
): EvaluatedNode {
// Short-circuit
if (evaluatedLeft.equals(trueConstant)) {
return trueConstant;
const rightType = this.typeComputer().computeType(rightOperand);
if (rightType.equals(this.coreTypes.Boolean)) {
return trueConstant;
}
}

// Compute the result if both operands are constant booleans
Expand All @@ -377,7 +386,10 @@ export class SafeDsPartialEvaluator {
): EvaluatedNode {
// Short-circuit
if (evaluatedLeft.equals(falseConstant)) {
return falseConstant;
const rightType = this.typeComputer().computeType(rightOperand);
if (rightType.equals(this.coreTypes.Boolean)) {
return falseConstant;
}
}

// Compute the result if both operands are constant booleans
Expand Down
13 changes: 13 additions & 0 deletions packages/safe-ds-lang/src/language/typing/safe-ds-core-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,19 @@ export class SafeDsCoreTypes {
return this.createCoreType(this.builtinClasses.Boolean);
}

Cell(wrappedType: Type = this.AnyOrNull): Type {
const cell = this.builtinClasses.Cell;
const wrappedTypeParameter = getTypeParameters(cell)[0];

if (!cell || !wrappedTypeParameter) {
/* c8 ignore next 2 */
return UnknownType;
}

let substitutions = new Map([[wrappedTypeParameter, wrappedType]]);
return new ClassType(cell, substitutions, false);
}

get Float(): Type {
return this.createCoreType(this.builtinClasses.Float);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -371,24 +371,23 @@ export class SafeDsTypeComputer {
return this.computeTypeOfIndexedAccess(node);
} else if (isSdsInfixOperation(node)) {
switch (node.operator) {
// Boolean operators
case 'or':
case 'and':
return this.coreTypes.Boolean;

// Equality operators
case '==':
case '!=':
case '===':
case '!==':
return this.coreTypes.Boolean;
case '==':
case '!=':

// Logical operators
case 'or':
case 'and':

// Comparison operators
case '<':
case '<=':
case '>=':
case '>':
return this.coreTypes.Boolean;
return this.computeTypeOfBooleanOperation(node);

// Arithmetic operators
case '+':
Expand Down Expand Up @@ -429,7 +428,7 @@ export class SafeDsTypeComputer {
} else if (isSdsPrefixOperation(node)) {
switch (node.operator) {
case 'not':
return this.coreTypes.Boolean;
return this.computeTypeOfBooleanPrefixOperation(node);
case '-':
return this.computeTypeOfArithmeticPrefixOperation(node);

Expand Down Expand Up @@ -509,11 +508,32 @@ export class SafeDsTypeComputer {
return UnknownType;
}

private computeTypeOfBooleanOperation(node: SdsInfixOperation): Type {
const leftOperandType = this.computeType(node.leftOperand);
const rightOperandType = this.computeType(node.rightOperand);
const cellType = this.coreTypes.Cell();

if (
this.typeChecker.isSubtypeOf(leftOperandType, cellType) ||
this.typeChecker.isSubtypeOf(rightOperandType, cellType)
) {
return this.coreTypes.Cell(this.coreTypes.Boolean);
} else {
return this.coreTypes.Boolean;
}
}

private computeTypeOfArithmeticInfixOperation(node: SdsInfixOperation): Type {
const leftOperandType = this.computeType(node.leftOperand);
const rightOperandType = this.computeType(node.rightOperand);
const cellType = this.coreTypes.Cell();

if (
this.typeChecker.isSubtypeOf(leftOperandType, cellType) ||
this.typeChecker.isSubtypeOf(rightOperandType, cellType)
) {
return this.coreTypes.Cell(this.coreTypes.Number);
} else if (
this.typeChecker.isSubtypeOf(leftOperandType, this.coreTypes.Int) &&
this.typeChecker.isSubtypeOf(rightOperandType, this.coreTypes.Int)
) {
Expand Down Expand Up @@ -596,10 +616,24 @@ export class SafeDsTypeComputer {
);
}

private computeTypeOfBooleanPrefixOperation(node: SdsPrefixOperation): Type {
const operandType = this.computeType(node.operand);
const cellType = this.coreTypes.Cell();

if (this.typeChecker.isSubtypeOf(operandType, cellType)) {
return this.coreTypes.Cell(this.coreTypes.Boolean);
} else {
return this.coreTypes.Boolean;
}
}

private computeTypeOfArithmeticPrefixOperation(node: SdsPrefixOperation): Type {
const operandType = this.computeType(node.operand);
const cellType = this.coreTypes.Cell();

if (this.typeChecker.isSubtypeOf(operandType, this.coreTypes.Int)) {
if (this.typeChecker.isSubtypeOf(operandType, cellType)) {
return this.coreTypes.Cell(this.coreTypes.Number);
} else if (this.typeChecker.isSubtypeOf(operandType, this.coreTypes.Int)) {
return this.coreTypes.Int;
} else {
return this.coreTypes.Float;
Expand Down
64 changes: 37 additions & 27 deletions packages/safe-ds-lang/src/language/validation/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,17 +161,27 @@ export const infixOperationOperandsMustHaveCorrectType = (services: SafeDsServic
return (node: SdsInfixOperation, accept: ValidationAcceptor): void => {
const leftType = typeComputer.computeType(node.leftOperand);
const rightType = typeComputer.computeType(node.rightOperand);
const cellType = coreTypes.Cell();

switch (node.operator) {
case 'or':
case 'and':
if (node.leftOperand && !typeChecker.isSubtypeOf(leftType, coreTypes.Boolean)) {
accept('error', `Expected type '${coreTypes.Boolean}' but got '${leftType}'.`, {
if (
node.leftOperand &&
!typeChecker.isSubtypeOf(leftType, coreTypes.Boolean) &&
!typeChecker.isSubtypeOf(leftType, cellType)
) {
accept('error', `This operator is not defined for type '${leftType}'.`, {
node: node.leftOperand,
code: CODE_TYPE_MISMATCH,
});
}
if (node.rightOperand && !typeChecker.isSubtypeOf(rightType, coreTypes.Boolean)) {
accept('error', `Expected type '${coreTypes.Boolean}' but got '${rightType}'.`, {
if (
node.rightOperand &&
!typeChecker.isSubtypeOf(rightType, coreTypes.Boolean) &&
!typeChecker.isSubtypeOf(rightType, cellType)
) {
accept('error', `This operator is not defined for type '${rightType}'.`, {
node: node.rightOperand,
code: CODE_TYPE_MISMATCH,
});
Expand Down Expand Up @@ -202,26 +212,24 @@ export const infixOperationOperandsMustHaveCorrectType = (services: SafeDsServic
if (
node.leftOperand &&
!typeChecker.isSubtypeOf(leftType, coreTypes.Float) &&
!typeChecker.isSubtypeOf(leftType, coreTypes.Int)
!typeChecker.isSubtypeOf(leftType, coreTypes.Int) &&
!typeChecker.isSubtypeOf(leftType, cellType)
) {
accept('error', `Expected type '${coreTypes.Float}' or '${coreTypes.Int}' but got '${leftType}'.`, {
accept('error', `This operator is not defined for type '${leftType}'.`, {
node: node.leftOperand,
code: CODE_TYPE_MISMATCH,
});
}
if (
node.rightOperand &&
!typeChecker.isSubtypeOf(rightType, coreTypes.Float) &&
!typeChecker.isSubtypeOf(rightType, coreTypes.Int)
!typeChecker.isSubtypeOf(rightType, coreTypes.Int) &&
!typeChecker.isSubtypeOf(rightType, cellType)
) {
accept(
'error',
`Expected type '${coreTypes.Float}' or '${coreTypes.Int}' but got '${rightType}'.`,
{
node: node.rightOperand,
code: CODE_TYPE_MISMATCH,
},
);
accept('error', `This operator is not defined for type '${rightType}'.`, {
node: node.rightOperand,
code: CODE_TYPE_MISMATCH,
});
}
return;
}
Expand Down Expand Up @@ -338,10 +346,15 @@ export const prefixOperationOperandMustHaveCorrectType = (services: SafeDsServic

return (node: SdsPrefixOperation, accept: ValidationAcceptor): void => {
const operandType = typeComputer.computeType(node.operand);
const cellType = coreTypes.Cell(coreTypes.AnyOrNull);

switch (node.operator) {
case 'not':
if (!typeChecker.isSubtypeOf(operandType, coreTypes.Boolean)) {
accept('error', `Expected type '${coreTypes.Boolean}' but got '${operandType}'.`, {
if (
!typeChecker.isSubtypeOf(operandType, coreTypes.Boolean) &&
!typeChecker.isSubtypeOf(operandType, cellType)
) {
accept('error', `This operator is not defined for type '${operandType}'.`, {
node,
property: 'operand',
code: CODE_TYPE_MISMATCH,
Expand All @@ -351,17 +364,14 @@ export const prefixOperationOperandMustHaveCorrectType = (services: SafeDsServic
case '-':
if (
!typeChecker.isSubtypeOf(operandType, coreTypes.Float) &&
!typeChecker.isSubtypeOf(operandType, coreTypes.Int)
!typeChecker.isSubtypeOf(operandType, coreTypes.Int) &&
!typeChecker.isSubtypeOf(operandType, cellType)
) {
accept(
'error',
`Expected type '${coreTypes.Float}' or '${coreTypes.Int}' but got '${operandType}'.`,
{
node,
property: 'operand',
code: CODE_TYPE_MISMATCH,
},
);
accept('error', `This operator is not defined for type '${operandType}'.`, {
node,
property: 'operand',
code: CODE_TYPE_MISMATCH,
});
}
return;
}
Expand Down
Loading