Skip to content

Commit

Permalink
feat: handle indexed access on columns and rows in type checker
Browse files Browse the repository at this point in the history
  • Loading branch information
lars-reimann committed Nov 1, 2024
1 parent d6ee42e commit 3752201
Show file tree
Hide file tree
Showing 7 changed files with 150 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,7 @@ export class SafeDsTypeChecker {
* Checks whether {@link type} is allowed as the type of the receiver of an indexed access.
*/
canBeAccessedByIndex = (type: Type): boolean => {
return this.isList(type) || this.isMap(type);
return this.isColumn(type) || this.isList(type) || this.isMap(type) || this.isRow(type);
};

/**
Expand Down Expand Up @@ -406,6 +406,21 @@ export class SafeDsTypeChecker {
}
};

/**
* Checks whether {@link type} is some kind of column (with any element type).
*/
isColumn(type: Type): type is ClassType | TypeVariable {
const columnOrNull = this.coreTypes.Column(UnknownType).withExplicitNullability(true);

return (
!type.equals(this.coreTypes.Nothing) &&
!type.equals(this.coreTypes.NothingOrNull) &&
this.isSubtypeOf(type, columnOrNull, {
ignoreTypeParameters: true,
})
);
}

/**
* Checks whether {@link type} is some kind of image.
*/
Expand Down Expand Up @@ -451,6 +466,19 @@ export class SafeDsTypeChecker {
);
}

/**
* Checks whether {@link type} is some kind of row.
*/
isRow(type: Type): type is ClassType | TypeVariable {
const rowOrNull = this.coreTypes.Row.withExplicitNullability(true);

return (
!type.equals(this.coreTypes.Nothing) &&
!type.equals(this.coreTypes.NothingOrNull) &&
this.isSubtypeOf(type, rowOrNull)
);
}

/**
* Checks whether {@link type} represents a tabular data structure (i.e., a table).
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ import {
attributeMustHaveTypeHint,
callReceiverMustBeCallable,
indexedAccessIndexMustHaveCorrectType,
indexedAccessReceiverMustBeListOrMap,
indexedAccessReceiverMustHaveCorrectType,
infixOperationOperandsMustHaveCorrectType,
listMustNotContainNamedTuples,
mapMustNotContainNamedTuples,
Expand Down Expand Up @@ -294,7 +294,7 @@ export const registerValidationChecks = function (services: SafeDsServices) {
SdsIndexedAccess: [
indexedAccessIndexMustBeValid(services),
indexedAccessIndexMustHaveCorrectType(services),
indexedAccessReceiverMustBeListOrMap(services),
indexedAccessReceiverMustHaveCorrectType(services),
],
SdsInfixOperation: [
divisionDivisorMustNotBeZero(services),
Expand Down
15 changes: 12 additions & 3 deletions packages/safe-ds-lang/src/language/validation/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ export const callReceiverMustBeCallable = (services: SafeDsServices) => {
};
};

export const indexedAccessReceiverMustBeListOrMap = (services: SafeDsServices) => {
export const indexedAccessReceiverMustHaveCorrectType = (services: SafeDsServices) => {
const typeChecker = services.typing.TypeChecker;
const typeComputer = services.typing.TypeComputer;

Expand All @@ -111,7 +111,7 @@ export const indexedAccessReceiverMustBeListOrMap = (services: SafeDsServices) =

const receiverType = typeComputer.computeType(node.receiver);
if (!typeChecker.canBeAccessedByIndex(receiverType)) {
accept('error', `Expected type 'List<T>' or 'Map<K, V>' but got '${receiverType}'.`, {
accept('error', `Indexed access is not defined for type '${receiverType}'.`, {
node: node.receiver,
code: CODE_TYPE_MISMATCH,
});
Expand All @@ -127,7 +127,7 @@ export const indexedAccessIndexMustHaveCorrectType = (services: SafeDsServices)

return (node: SdsIndexedAccess, accept: ValidationAcceptor): void => {
const receiverType = typeComputer.computeType(node.receiver);
if (typeChecker.isList(receiverType)) {
if (typeChecker.isColumn(receiverType) || typeChecker.isList(receiverType)) {
const indexType = typeComputer.computeType(node.index);
if (!typeChecker.isSubtypeOf(indexType, coreTypes.Int)) {
accept('error', `Expected type '${coreTypes.Int}' but got '${indexType}'.`, {
Expand All @@ -136,6 +136,15 @@ export const indexedAccessIndexMustHaveCorrectType = (services: SafeDsServices)
code: CODE_TYPE_MISMATCH,
});
}
} else if (typeChecker.isRow(receiverType)) {
const indexType = typeComputer.computeType(node.index);
if (!typeChecker.isSubtypeOf(indexType, coreTypes.String)) {
accept('error', `Expected type '${coreTypes.String}' but got '${indexType}'.`, {
node,
property: 'index',
code: CODE_TYPE_MISMATCH,
});
}
} else if (receiverType instanceof ClassType || receiverType instanceof TypeVariable) {
const mapType = typeComputer.computeMatchingSupertype(receiverType, coreClasses.Map);
if (mapType) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package tests.validation.types.checking.indexedAccessOnColumn

@Pure fun column() -> column: Column<Int>
@Pure fun index() -> index: Int

pipeline myPipeline {
// $TEST$ no error r"Expected type .* but got .*\."
column()[»0«];

// $TEST$ error "Expected type 'Int' but got 'literal<"">'."
column()[»""«];

// $TEST$ no error r"Expected type .* but got .*\."
column()[»index()«];

// $TEST$ no error r"Expected type .* but got .*\."
unresolved[»""«];
}

class MyColumn sub Column<Int>

segment mySegment(
myColumn: MyColumn,
) {
// $TEST$ no error r"Expected type .* but got .*\."
myColumn[»0«];

// $TEST$ error "Expected type 'Int' but got 'literal<"">'."
myColumn[»""«];

// $TEST$ no error r"Expected type .* but got .*\."
myColumn[»index()«];
}

// Strict checking of type parameter types
class MyClass<T sub Any>(
p1: T,

// $TEST$ error "Expected type 'Int' but got 'T'."
a: Any? = column()[»p1«],
)
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,21 @@ pipeline myPipeline {
unresolved[»""«];
}

class MyList sub List<Int>

segment mySegment(
myList: MyList,
) {
// $TEST$ no error r"Expected type .* but got .*\."
myList[»0«];

// $TEST$ error "Expected type 'Int' but got 'literal<"">'."
myList[»""«];

// $TEST$ no error r"Expected type .* but got .*\."
myList[»index()«];
}

// Strict checking of type parameter types
class MyClass<T sub Any>(
p1: T,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package tests.validation.types.checking.indexedAccessOnRow

@Pure fun row() -> row: Row
@Pure fun columnName() -> name: String

pipeline myPipeline {
// $TEST$ no error r"Expected type .* but got .*\."
row()[»""«];

// $TEST$ error "Expected type 'String' but got 'literal<1>'."
row()[»1«];

// $TEST$ no error r"Expected type .* but got .*\."
row()[»columnName()«];

// $TEST$ no error r"Expected type .* but got .*\."
unresolved[»""«];
}

class MyRow sub Row

segment mySegment(
myRow: MyRow,
) {
// $TEST$ no error r"Expected type .* but got .*\."
myRow[»""«];

// $TEST$ error "Expected type 'String' but got 'literal<1>'."
myRow[»1«];

// $TEST$ no error r"Expected type .* but got .*\."
myRow[»columnName()«];
}

// Strict checking of type parameter types
class MyClass<T sub Any>(
p1: T,

// $TEST$ error "Expected type 'String' but got 'T'."
a: Any? = row()[»p1«],
)
Original file line number Diff line number Diff line change
Expand Up @@ -11,32 +11,32 @@ segment mySegment(
myList: MyList,
myMap: MyMap,
) {
// $TEST$ no error r"Expected type 'List<T>' or 'Map<K, V>' but got .*\."
// $TEST$ no error r"Indexed access is not defined for type .*\."
»[1]«[0];
// $TEST$ no error r"Expected type 'List<T>' or 'Map<K, V>' but got .*\."
// $TEST$ no error r"Indexed access is not defined for type .*\."
»{0: 1}«[0];
// $TEST$ error "Expected type 'List<T>' or 'Map<K, V>' but got 'literal<1>'."
// $TEST$ error "Indexed access is not defined for type 'literal<1>'."
»1«[0];

// $TEST$ no error r"Expected type 'List<T>' or 'Map<K, V>' but got .*\."
// $TEST$ no error r"Indexed access is not defined for type .*\."
»listOrNull«[0];
// $TEST$ no error r"Expected type 'List<T>' or 'Map<K, V>' but got .*\."
// $TEST$ no error r"Indexed access is not defined for type .*\."
»mapOrNull«[""];
// $TEST$ error "Expected type 'List<T>' or 'Map<K, V>' but got 'Int?'."
// $TEST$ error "Indexed access is not defined for type 'Int?'."
»intOrNull«[0];

// $TEST$ no error r"Expected type 'List<T>' or 'Map<K, V>' but got .*\."
// $TEST$ no error r"Indexed access is not defined for type .*\."
»myList«[0];
// $TEST$ no error r"Expected type 'List<T>' or 'Map<K, V>' but got .*\."
// $TEST$ no error r"Indexed access is not defined for type .*\."
»myMap«[""];
// $TEST$ error "Expected type 'List<T>' or 'Map<K, V>' but got 'unknown'."
// $TEST$ error "Indexed access is not defined for type 'unknown'."
»unresolved«[0];
}

// Strict checking of type parameter types
class MyClass<T sub Any>(
p1: T,

// $TEST$ error "Expected type 'List<T>' or 'Map<K, V>' but got 'T'."
// $TEST$ error "Indexed access is not defined for type 'T'."
p2: Any? = »p1«[0],
)

0 comments on commit 3752201

Please sign in to comment.