diff --git a/docs/api/safeds/data/tabular/containers/Column.md b/docs/api/safeds/data/tabular/containers/Column.md index 61baf0a73..c569a8b7b 100644 --- a/docs/api/safeds/data/tabular/containers/Column.md +++ b/docs/api/safeds/data/tabular/containers/Column.md @@ -78,7 +78,7 @@ pipeline example { ) -> distinctValues: List /** - * Return the column value at specified index. This WILL LATER BE equivalent to the `[]` operator (indexed access). + * Return the column value at specified index. This is equivalent to the `[]` operator (indexed access). * * Nonnegative indices are counted from the beginning (starting at 0), negative indices from the end (starting at * -1). @@ -92,6 +92,12 @@ pipeline example { * val column = Column("test", [1, 2, 3]); * val result = column.getValue(1); // 2 * } + * + * @example + * pipeline example { + * val column = Column("test", [1, 2, 3]); + * val result = column[1]; // 2 + * } */ @Pure @PythonName("get_value") @@ -695,7 +701,7 @@ pipeline example { ??? quote "Stub code in `Column.sdsstub`" - ```sds linenums="126" + ```sds linenums="132" @Pure fun all( predicate: (cell: Cell) -> satisfiesPredicate: Cell, @@ -754,7 +760,7 @@ pipeline example { ??? quote "Stub code in `Column.sdsstub`" - ```sds linenums="169" + ```sds linenums="175" @Pure fun any( predicate: (cell: Cell) -> satisfiesPredicate: Cell, @@ -804,7 +810,7 @@ pipeline example { ??? quote "Stub code in `Column.sdsstub`" - ```sds linenums="341" + ```sds linenums="347" @Pure @PythonName("correlation_with") fun correlationWith( @@ -858,7 +864,7 @@ pipeline example { ??? quote "Stub code in `Column.sdsstub`" - ```sds linenums="207" + ```sds linenums="213" @Pure fun countIf( predicate: (cell: Cell) -> satisfiesPredicate: Cell, @@ -893,7 +899,7 @@ pipeline example { ??? quote "Stub code in `Column.sdsstub`" - ```sds linenums="360" + ```sds linenums="366" @Pure @PythonName("distinct_value_count") fun distinctValueCount( @@ -927,7 +933,7 @@ pipeline example { ## `getValue` {#safeds.data.tabular.containers.Column.getValue data-toc-label='[function] getValue'} -Return the column value at specified index. This WILL LATER BE equivalent to the `[]` operator (indexed access). +Return the column value at specified index. This is equivalent to the `[]` operator (indexed access). Nonnegative indices are counted from the beginning (starting at 0), negative indices from the end (starting at -1). @@ -952,10 +958,16 @@ pipeline example { val result = column.getValue(1); // 2 } ``` +```sds +pipeline example { + val column = Column("test", [1, 2, 3]); + val result = column[1]; // 2 +} +``` ??? quote "Stub code in `Column.sdsstub`" - ```sds linenums="83" + ```sds linenums="89" @Pure @PythonName("get_value") fun getValue( @@ -996,7 +1008,7 @@ pipeline example { ??? quote "Stub code in `Column.sdsstub`" - ```sds linenums="389" + ```sds linenums="395" @Pure fun idness() -> idness: Float ``` @@ -1022,7 +1034,7 @@ pipeline example { ??? quote "Stub code in `Column.sdsstub`" - ```sds linenums="403" + ```sds linenums="409" @Pure fun max() -> max: T? ``` @@ -1050,7 +1062,7 @@ pipeline example { ??? quote "Stub code in `Column.sdsstub`" - ```sds linenums="419" + ```sds linenums="425" @Pure fun mean() -> mean: T ``` @@ -1079,7 +1091,7 @@ pipeline example { ??? quote "Stub code in `Column.sdsstub`" - ```sds linenums="436" + ```sds linenums="442" @Pure fun median() -> median: T ``` @@ -1105,7 +1117,7 @@ pipeline example { ??? quote "Stub code in `Column.sdsstub`" - ```sds linenums="450" + ```sds linenums="456" @Pure fun min() -> min: T? ``` @@ -1131,7 +1143,7 @@ pipeline example { ??? quote "Stub code in `Column.sdsstub`" - ```sds linenums="464" + ```sds linenums="470" @Pure @PythonName("missing_value_count") fun missingValueCount() -> missingValueCount: Int @@ -1164,7 +1176,7 @@ pipeline example { ??? quote "Stub code in `Column.sdsstub`" - ```sds linenums="485" + ```sds linenums="491" @Pure @PythonName("missing_value_ratio") fun missingValueRatio() -> missingValueRatio: Float @@ -1200,7 +1212,7 @@ pipeline example { ??? quote "Stub code in `Column.sdsstub`" - ```sds linenums="505" + ```sds linenums="511" @Pure fun mode( @PythonName("ignore_missing_values") ignoreMissingValues: Boolean = true, @@ -1258,7 +1270,7 @@ pipeline example { ??? quote "Stub code in `Column.sdsstub`" - ```sds linenums="250" + ```sds linenums="256" @Pure fun none( predicate: (cell: Cell) -> satisfiesPredicate: Cell, @@ -1296,7 +1308,7 @@ pipeline example { ??? quote "Stub code in `Column.sdsstub`" - ```sds linenums="272" + ```sds linenums="278" @Pure fun rename( @PythonName("new_name") newName: String @@ -1330,7 +1342,7 @@ pipeline example { ??? quote "Stub code in `Column.sdsstub`" - ```sds linenums="527" + ```sds linenums="533" @Pure fun stability() -> stability: Float ``` @@ -1358,7 +1370,7 @@ pipeline example { ??? quote "Stub code in `Column.sdsstub`" - ```sds linenums="544" + ```sds linenums="550" @Pure @PythonName("standard_deviation") fun standardDeviation() -> standardDeviation: Float @@ -1385,7 +1397,7 @@ pipeline example { ??? quote "Stub code in `Column.sdsstub`" - ```sds linenums="309" + ```sds linenums="315" @Pure @PythonName("summarize_statistics") fun summarizeStatistics() -> statistics: Table @@ -1412,7 +1424,7 @@ pipeline example { ??? quote "Stub code in `Column.sdsstub`" - ```sds linenums="576" + ```sds linenums="582" @Pure @PythonName("to_list") fun toList() -> values: List @@ -1440,7 +1452,7 @@ pipeline example { ??? quote "Stub code in `Column.sdsstub`" - ```sds linenums="592" + ```sds linenums="598" @Pure @PythonName("to_table") fun toTable() -> table: Table @@ -1482,7 +1494,7 @@ pipeline example { ??? quote "Stub code in `Column.sdsstub`" - ```sds linenums="293" + ```sds linenums="299" @Pure fun transform( transformer: (cell: Cell) -> transformedCell: Cell @@ -1512,7 +1524,7 @@ pipeline example { ??? quote "Stub code in `Column.sdsstub`" - ```sds linenums="562" + ```sds linenums="568" @Pure fun variance() -> variance: Float ``` diff --git a/docs/api/safeds/data/tabular/containers/Row.md b/docs/api/safeds/data/tabular/containers/Row.md index 00986ff81..d43d6bab8 100644 --- a/docs/api/safeds/data/tabular/containers/Row.md +++ b/docs/api/safeds/data/tabular/containers/Row.md @@ -29,7 +29,7 @@ This class cannot be instantiated directly. It is only used for arguments of cal attr ^schema: Schema /** - * Get the value of the specified column. This WILL LATER BE equivalent to using the `[]` operator (indexed access). + * Get the value of the specified column. This is equivalent to the `[]` operator (indexed access). * * @param name The name of the column. * @@ -39,6 +39,12 @@ This class cannot be instantiated directly. It is only used for arguments of cal * pipeline example { * val table = Table({"col1": [1, 2], "col2": [3, 4]}); * val result = table.removeRows((row) -> row.getValue("col1") == 1); + * } + * + * @example + * pipeline example { + * val table = Table({"col1": [1, 2], "col2": [3, 4]}); + * val result = table.removeRows((row) -> row["col1"] == 1); * // Table({"col1": [2], "col2": [4]}) * } */ @@ -46,7 +52,7 @@ This class cannot be instantiated directly. It is only used for arguments of cal @PythonName("get_value") fun getValue( name: String - ) -> value: Cell + ) -> value: Cell /** * Get the type of the specified column. @@ -112,7 +118,7 @@ Get the type of the specified column. ??? quote "Stub code in `Row.sdsstub`" - ```sds linenums="52" + ```sds linenums="58" @Pure @PythonName("get_column_type") fun getColumnType( @@ -122,7 +128,7 @@ Get the type of the specified column. ## `getValue` {#safeds.data.tabular.containers.Row.getValue data-toc-label='[function] getValue'} -Get the value of the specified column. This WILL LATER BE equivalent to using the `[]` operator (indexed access). +Get the value of the specified column. This is equivalent to the `[]` operator (indexed access). **Parameters:** @@ -134,7 +140,7 @@ Get the value of the specified column. This WILL LATER BE equivalent to using th | Name | Type | Description | |------|------|-------------| -| `value` | [`Cell`][safeds.data.tabular.containers.Cell] | The value of the column. | +| `value` | [`Cell`][safeds.data.tabular.containers.Cell] | The value of the column. | **Examples:** @@ -142,18 +148,24 @@ Get the value of the specified column. This WILL LATER BE equivalent to using th pipeline example { val table = Table({"col1": [1, 2], "col2": [3, 4]}); val result = table.removeRows((row) -> row.getValue("col1") == 1); +} +``` +```sds +pipeline example { + val table = Table({"col1": [1, 2], "col2": [3, 4]}); + val result = table.removeRows((row) -> row["col1"] == 1); // Table({"col1": [2], "col2": [4]}) } ``` ??? quote "Stub code in `Row.sdsstub`" - ```sds linenums="39" + ```sds linenums="45" @Pure @PythonName("get_value") fun getValue( name: String - ) -> value: Cell + ) -> value: Cell ``` ## `hasColumn` {#safeds.data.tabular.containers.Row.hasColumn data-toc-label='[function] hasColumn'} @@ -174,7 +186,7 @@ Check if the row has a column with the specified name. ??? quote "Stub code in `Row.sdsstub`" - ```sds linenums="65" + ```sds linenums="71" @Pure @PythonName("has_column") fun hasColumn( diff --git a/docs/api/safeds/data/tabular/containers/Table.md b/docs/api/safeds/data/tabular/containers/Table.md index cd5fd4e8d..d9cde7f20 100644 --- a/docs/api/safeds/data/tabular/containers/Table.md +++ b/docs/api/safeds/data/tabular/containers/Table.md @@ -190,7 +190,7 @@ pipeline example { * @example * pipeline example { * val table = Table({"a": [1, 2, 3], "b": [4, 5, 6]}); - * val result = table.addComputedColumn("c", (row) -> row.getValue("a") + row.getValue("b")); + * val result = table.addComputedColumn("c", (row) -> row["a"] + row["b"]); * } */ @Pure @@ -468,13 +468,13 @@ pipeline example { * @example * pipeline example { * val table = Table({"col1": [1, 2, 3], "col2": [1, 3, 3]}); - * val result = table.countRowIf((row) -> row.getValue("col1") == row.getValue("col2")); // 2 + * val result = table.countRowIf((row) -> row["col1"] == row["col2"]); // 2 * } * * @example * pipeline example { * val table = Table({"col1": [1, 2, 3], "col2": [1, 3, 3]}); - * val result = table.countRowIf((row) -> row.getValue("col1") > row.getValue("col2")); // 0 + * val result = table.countRowIf((row) -> row["col1"] > row["col2"]); // 0 * } */ @Pure @@ -514,7 +514,7 @@ pipeline example { * @example * pipeline example { * val table = Table({"a": [1, 2, 3], "b": [4, 5, 6]}); - * val result = table.removeRows((row) -> row.getValue("a") == 2); + * val result = table.removeRows((row) -> row["a"] == 2); * // Table({"a": [1, 3], "b": [4, 6]}) * } */ @@ -673,7 +673,7 @@ pipeline example { * @example * pipeline example { * val table = Table({"a": [2, 1, 3], "b": [1, 1, 2]}); - * val result = table.sortRows((row) -> row.getValue("a") - row.getValue("b")); + * val result = table.sortRows((row) -> row["a"] - row["b"]); * // Table({"a": [1, 2, 3], "b": [1, 1, 2]}) * } */ @@ -1147,7 +1147,7 @@ Return a new table with an additional computed column. ```sds hl_lines="3" pipeline example { val table = Table({"a": [1, 2, 3], "b": [4, 5, 6]}); - val result = table.addComputedColumn("c", (row) -> row.getValue("a") + row.getValue("b")); + val result = table.addComputedColumn("c", (row) -> row["a"] + row["b"]); } ``` @@ -1280,13 +1280,13 @@ if the predicate returns null at least once. Otherwise, it still returns how oft ```sds hl_lines="3" pipeline example { val table = Table({"col1": [1, 2, 3], "col2": [1, 3, 3]}); - val result = table.countRowIf((row) -> row.getValue("col1") == row.getValue("col2")); // 2 + val result = table.countRowIf((row) -> row["col1"] == row["col2"]); // 2 } ``` ```sds hl_lines="3" pipeline example { val table = Table({"col1": [1, 2, 3], "col2": [1, 3, 3]}); - val result = table.countRowIf((row) -> row.getValue("col1") > row.getValue("col2")); // 0 + val result = table.countRowIf((row) -> row["col1"] > row["col2"]); // 0 } ``` @@ -1690,7 +1690,7 @@ Return a new table without rows that satisfy a condition. ```sds hl_lines="3" pipeline example { val table = Table({"a": [1, 2, 3], "b": [4, 5, 6]}); - val result = table.removeRows((row) -> row.getValue("a") == 2); + val result = table.removeRows((row) -> row["a"] == 2); // Table({"a": [1, 3], "b": [4, 6]}) } ``` @@ -2044,7 +2044,7 @@ Return a new table with the rows sorted. ```sds hl_lines="3" pipeline example { val table = Table({"a": [2, 1, 3], "b": [1, 1, 2]}); - val result = table.sortRows((row) -> row.getValue("a") - row.getValue("b")); + val result = table.sortRows((row) -> row["a"] - row["b"]); // Table({"a": [1, 2, 3], "b": [1, 1, 2]}) } ``` diff --git a/packages/safe-ds-lang/src/language/builtins/safe-ds-classes.ts b/packages/safe-ds-lang/src/language/builtins/safe-ds-classes.ts index 2ca1f3ba0..d896b9e8e 100644 --- a/packages/safe-ds-lang/src/language/builtins/safe-ds-classes.ts +++ b/packages/safe-ds-lang/src/language/builtins/safe-ds-classes.ts @@ -4,8 +4,10 @@ import { resourceNameToUri } from '../../helpers/resources.js'; import { URI } from 'langium'; const CELL_URI = resourceNameToUri('builtins/safeds/data/tabular/containers/Cell.sdsstub'); +const COLUMN_URI = resourceNameToUri('builtins/safeds/data/tabular/containers/Column.sdsstub'); const CORE_CLASSES_URI = resourceNameToUri('builtins/safeds/lang/coreClasses.sdsstub'); const IMAGE_URI = resourceNameToUri('builtins/safeds/data/image/containers/Image.sdsstub'); +const ROW_URI = resourceNameToUri('builtins/safeds/data/tabular/containers/Row.sdsstub'); const TABLE_URI = resourceNameToUri('builtins/safeds/data/tabular/containers/Table.sdsstub'); export class SafeDsClasses extends SafeDsModuleMembers { @@ -21,6 +23,10 @@ export class SafeDsClasses extends SafeDsModuleMembers { return this.getClass('Cell', CELL_URI); } + get Column(): SdsClass | undefined { + return this.getClass('Column', COLUMN_URI); + } + get Float(): SdsClass | undefined { return this.getClass('Float'); } @@ -49,6 +55,10 @@ export class SafeDsClasses extends SafeDsModuleMembers { return this.getClass('Number'); } + get Row(): SdsClass | undefined { + return this.getClass('Row', ROW_URI); + } + get String(): SdsClass | undefined { return this.getClass('String'); } diff --git a/packages/safe-ds-lang/src/language/typing/safe-ds-core-types.ts b/packages/safe-ds-lang/src/language/typing/safe-ds-core-types.ts index 4be035de4..169789627 100644 --- a/packages/safe-ds-lang/src/language/typing/safe-ds-core-types.ts +++ b/packages/safe-ds-lang/src/language/typing/safe-ds-core-types.ts @@ -39,6 +39,19 @@ export class SafeDsCoreTypes { return new ClassType(cell, substitutions, false); } + Column(elementType: Type): Type { + const column = this.builtinClasses.Column; + const elementTypeParameter = getTypeParameters(column)[0]; + + if (!column || !elementTypeParameter) { + /* c8 ignore next 2 */ + return UnknownType; + } + + let substitutions = new Map([[elementTypeParameter, elementType]]); + return new ClassType(column, substitutions, false); + } + get Float(): Type { return this.createCoreType(this.builtinClasses.Float); } @@ -93,6 +106,10 @@ export class SafeDsCoreTypes { return this.createCoreType(this.builtinClasses.Number); } + get Row(): Type { + return this.createCoreType(this.builtinClasses.Row); + } + get String(): Type { return this.createCoreType(this.builtinClasses.String); } diff --git a/packages/safe-ds-lang/src/language/typing/safe-ds-type-checker.ts b/packages/safe-ds-lang/src/language/typing/safe-ds-type-checker.ts index 2eecf9f68..87d4962aa 100644 --- a/packages/safe-ds-lang/src/language/typing/safe-ds-type-checker.ts +++ b/packages/safe-ds-lang/src/language/typing/safe-ds-type-checker.ts @@ -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); }; /** @@ -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. */ @@ -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). */ diff --git a/packages/safe-ds-lang/src/language/typing/safe-ds-type-computer.ts b/packages/safe-ds-lang/src/language/typing/safe-ds-type-computer.ts index c1495eab0..6eefcb5cb 100644 --- a/packages/safe-ds-lang/src/language/typing/safe-ds-type-computer.ts +++ b/packages/safe-ds-lang/src/language/typing/safe-ds-type-computer.ts @@ -489,20 +489,37 @@ export class SafeDsTypeComputer { return UnknownType; } + // Receiver is a column + const columnType = this.computeMatchingSupertype(receiverType, this.coreClasses.Column); + if (columnType) { + const elementType = columnType.getTypeParameterTypeByIndex(0); + return elementType.withExplicitNullability( + elementType.isExplicitlyNullable || (columnType.isExplicitlyNullable && node.isNullSafe), + ); + } + // Receiver is a list const listType = this.computeMatchingSupertype(receiverType, this.coreClasses.List); if (listType) { - return listType - .getTypeParameterTypeByIndex(0) - .withExplicitNullability(listType.isExplicitlyNullable && node.isNullSafe); + const elementType = listType.getTypeParameterTypeByIndex(0); + return elementType.withExplicitNullability( + elementType.isExplicitlyNullable || (listType.isExplicitlyNullable && node.isNullSafe), + ); } // Receiver is a map const mapType = this.computeMatchingSupertype(receiverType, this.coreClasses.Map); if (mapType) { - return mapType - .getTypeParameterTypeByIndex(1) - .withExplicitNullability(mapType.isExplicitlyNullable && node.isNullSafe); + const valueType = mapType.getTypeParameterTypeByIndex(1); + return valueType.withExplicitNullability( + valueType.isExplicitlyNullable || (mapType.isExplicitlyNullable && node.isNullSafe), + ); + } + + // Receiver is a row + const rowType = this.computeMatchingSupertype(receiverType, this.coreClasses.Row); + if (rowType) { + return this.coreTypes.Cell().withExplicitNullability(rowType.isExplicitlyNullable && node.isNullSafe); } return UnknownType; diff --git a/packages/safe-ds-lang/src/language/validation/safe-ds-validator.ts b/packages/safe-ds-lang/src/language/validation/safe-ds-validator.ts index 86572da0e..038c438ad 100644 --- a/packages/safe-ds-lang/src/language/validation/safe-ds-validator.ts +++ b/packages/safe-ds-lang/src/language/validation/safe-ds-validator.ts @@ -160,7 +160,7 @@ import { attributeMustHaveTypeHint, callReceiverMustBeCallable, indexedAccessIndexMustHaveCorrectType, - indexedAccessReceiverMustBeListOrMap, + indexedAccessReceiverMustHaveCorrectType, infixOperationOperandsMustHaveCorrectType, listMustNotContainNamedTuples, mapMustNotContainNamedTuples, @@ -294,7 +294,7 @@ export const registerValidationChecks = function (services: SafeDsServices) { SdsIndexedAccess: [ indexedAccessIndexMustBeValid(services), indexedAccessIndexMustHaveCorrectType(services), - indexedAccessReceiverMustBeListOrMap(services), + indexedAccessReceiverMustHaveCorrectType(services), ], SdsInfixOperation: [ divisionDivisorMustNotBeZero(services), diff --git a/packages/safe-ds-lang/src/language/validation/types.ts b/packages/safe-ds-lang/src/language/validation/types.ts index e9db69dca..869e99185 100644 --- a/packages/safe-ds-lang/src/language/validation/types.ts +++ b/packages/safe-ds-lang/src/language/validation/types.ts @@ -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; @@ -111,7 +111,7 @@ export const indexedAccessReceiverMustBeListOrMap = (services: SafeDsServices) = const receiverType = typeComputer.computeType(node.receiver); if (!typeChecker.canBeAccessedByIndex(receiverType)) { - accept('error', `Expected type 'List' or 'Map' but got '${receiverType}'.`, { + accept('error', `Indexed access is not defined for type '${receiverType}'.`, { node: node.receiver, code: CODE_TYPE_MISMATCH, }); @@ -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}'.`, { @@ -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) { diff --git a/packages/safe-ds-lang/src/resources/builtins/safeds/data/tabular/containers/Column.sdsstub b/packages/safe-ds-lang/src/resources/builtins/safeds/data/tabular/containers/Column.sdsstub index 4861b9aff..d0ab3d850 100644 --- a/packages/safe-ds-lang/src/resources/builtins/safeds/data/tabular/containers/Column.sdsstub +++ b/packages/safe-ds-lang/src/resources/builtins/safeds/data/tabular/containers/Column.sdsstub @@ -65,7 +65,7 @@ class Column( ) -> distinctValues: List /** - * Return the column value at specified index. This WILL LATER BE equivalent to the `[]` operator (indexed access). + * Return the column value at specified index. This is equivalent to the `[]` operator (indexed access). * * Nonnegative indices are counted from the beginning (starting at 0), negative indices from the end (starting at * -1). @@ -79,6 +79,12 @@ class Column( * val column = Column("test", [1, 2, 3]); * val result = column.getValue(1); // 2 * } + * + * @example + * pipeline example { + * val column = Column("test", [1, 2, 3]); + * val result = column[1]; // 2 + * } */ @Pure @PythonName("get_value") diff --git a/packages/safe-ds-lang/src/resources/builtins/safeds/data/tabular/containers/Row.sdsstub b/packages/safe-ds-lang/src/resources/builtins/safeds/data/tabular/containers/Row.sdsstub index be31314c7..f11b3e0e6 100644 --- a/packages/safe-ds-lang/src/resources/builtins/safeds/data/tabular/containers/Row.sdsstub +++ b/packages/safe-ds-lang/src/resources/builtins/safeds/data/tabular/containers/Row.sdsstub @@ -23,7 +23,7 @@ class Row { attr ^schema: Schema /** - * Get the value of the specified column. This WILL LATER BE equivalent to using the `[]` operator (indexed access). + * Get the value of the specified column. This is equivalent to the `[]` operator (indexed access). * * @param name The name of the column. * @@ -33,6 +33,12 @@ class Row { * pipeline example { * val table = Table({"col1": [1, 2], "col2": [3, 4]}); * val result = table.removeRows((row) -> row.getValue("col1") == 1); + * } + * + * @example + * pipeline example { + * val table = Table({"col1": [1, 2], "col2": [3, 4]}); + * val result = table.removeRows((row) -> row["col1"] == 1); * // Table({"col1": [2], "col2": [4]}) * } */ @@ -40,7 +46,7 @@ class Row { @PythonName("get_value") fun getValue( name: String - ) -> value: Cell + ) -> value: Cell /** * Get the type of the specified column. diff --git a/packages/safe-ds-lang/src/resources/builtins/safeds/data/tabular/containers/Table.sdsstub b/packages/safe-ds-lang/src/resources/builtins/safeds/data/tabular/containers/Table.sdsstub index 07f3cec08..b5fd326d7 100644 --- a/packages/safe-ds-lang/src/resources/builtins/safeds/data/tabular/containers/Table.sdsstub +++ b/packages/safe-ds-lang/src/resources/builtins/safeds/data/tabular/containers/Table.sdsstub @@ -188,7 +188,7 @@ class Table( * @example * pipeline example { * val table = Table({"a": [1, 2, 3], "b": [4, 5, 6]}); - * val result = table.addComputedColumn("c", (row) -> row.getValue("a") + row.getValue("b")); + * val result = table.addComputedColumn("c", (row) -> row["a"] + row["b"]); * } */ @Pure @@ -466,13 +466,13 @@ class Table( * @example * pipeline example { * val table = Table({"col1": [1, 2, 3], "col2": [1, 3, 3]}); - * val result = table.countRowIf((row) -> row.getValue("col1") == row.getValue("col2")); // 2 + * val result = table.countRowIf((row) -> row["col1"] == row["col2"]); // 2 * } * * @example * pipeline example { * val table = Table({"col1": [1, 2, 3], "col2": [1, 3, 3]}); - * val result = table.countRowIf((row) -> row.getValue("col1") > row.getValue("col2")); // 0 + * val result = table.countRowIf((row) -> row["col1"] > row["col2"]); // 0 * } */ @Pure @@ -512,7 +512,7 @@ class Table( * @example * pipeline example { * val table = Table({"a": [1, 2, 3], "b": [4, 5, 6]}); - * val result = table.removeRows((row) -> row.getValue("a") == 2); + * val result = table.removeRows((row) -> row["a"] == 2); * // Table({"a": [1, 3], "b": [4, 6]}) * } */ @@ -671,7 +671,7 @@ class Table( * @example * pipeline example { * val table = Table({"a": [2, 1, 3], "b": [1, 1, 2]}); - * val result = table.sortRows((row) -> row.getValue("a") - row.getValue("b")); + * val result = table.sortRows((row) -> row["a"] - row["b"]); * // Table({"a": [1, 2, 3], "b": [1, 1, 2]}) * } */ diff --git a/packages/safe-ds-lang/tests/resources/typing/expressions/indexed accesses/on columns/main.sdsdev b/packages/safe-ds-lang/tests/resources/typing/expressions/indexed accesses/on columns/main.sdsdev new file mode 100644 index 000000000..620c5f12c --- /dev/null +++ b/packages/safe-ds-lang/tests/resources/typing/expressions/indexed accesses/on columns/main.sdsdev @@ -0,0 +1,97 @@ +package tests.typing.expressions.indexedAccesses.onColumns + +class IntColumn sub Column + +segment mySegment(param1: Column, param2: Column, param3: Column?, param4: IntColumn) { + // $TEST$ serialization Int + »param1[0]«; + + // $TEST$ serialization Int + »param1[unresolved]«; + + // $TEST$ serialization Int? + »param2[0]«; + + // $TEST$ serialization Int? + »param2[unresolved]«; + + // $TEST$ serialization Int + »param3[0]«; + + // $TEST$ serialization Int + »param3[unresolved]«; + + // $TEST$ serialization Int + »param4[0]«; + + // $TEST$ serialization Int + »param4[unresolved]«; + + + // $TEST$ serialization Int + »param1?[0]«; + + // $TEST$ serialization Int + »param1?[unresolved]«; + + // $TEST$ serialization Int? + »param2?[0]«; + + // $TEST$ serialization Int? + »param2?[unresolved]«; + + // $TEST$ serialization Int? + »param3?[0]«; + + // $TEST$ serialization Int? + »param3?[unresolved]«; + + // $TEST$ serialization Int + »param4?[0]«; + + // $TEST$ serialization Int + »param4?[unresolved]«; +} + +class MyClass, MyNullableColumn sub Column?>( + param1: MyColumn, + param2: MyColumn?, + param3: MyNullableColumn, + + // $TEST$ serialization Int + p1: Any? = »param1[0]«, + + // $TEST$ serialization Int + p2: Any? = »param1[unresolved]«, + + // $TEST$ serialization Int + p3: Any? = »param2[0]«, + + // $TEST$ serialization Int + p4: Any? = »param2[unresolved]«, + + // $TEST$ serialization Int + p5: Any? = »param3[0]«, + + // $TEST$ serialization Int + p6: Any? = »param3[unresolved]«, + + + // $TEST$ serialization Int + p7: Any? = »param1?[0]«, + + // $TEST$ serialization Int + p8: Any? = »param1?[unresolved]«, + + // $TEST$ serialization Int? + p9: Any? = »param2?[0]«, + + // $TEST$ serialization Int? + p10: Any? = »param2?[unresolved]«, + + // $TEST$ serialization Int? + p11: Any? = »param3?[0]«, + + // $TEST$ serialization Int? + p12: Any? = »param3?[unresolved]«, +) diff --git a/packages/safe-ds-lang/tests/resources/typing/expressions/indexed accesses/on lists/main.sdsdev b/packages/safe-ds-lang/tests/resources/typing/expressions/indexed accesses/on lists/main.sdsdev index 99e63e19a..cc3124955 100644 --- a/packages/safe-ds-lang/tests/resources/typing/expressions/indexed accesses/on lists/main.sdsdev +++ b/packages/safe-ds-lang/tests/resources/typing/expressions/indexed accesses/on lists/main.sdsdev @@ -2,17 +2,17 @@ package tests.typing.expressions.indexedAccesses.onLists class IntList sub List -segment mySegment(param1: List, param2: List?, param3: IntList) { +segment mySegment(param1: List, param2: List, param3: List?, param4: IntList) { // $TEST$ serialization Int »param1[0]«; // $TEST$ serialization Int »param1[unresolved]«; - // $TEST$ serialization Int + // $TEST$ serialization Int? »param2[0]«; - // $TEST$ serialization Int + // $TEST$ serialization Int? »param2[unresolved]«; // $TEST$ serialization Int @@ -21,6 +21,12 @@ segment mySegment(param1: List, param2: List?, param3: IntList) { // $TEST$ serialization Int »param3[unresolved]«; + // $TEST$ serialization Int + »param4[0]«; + + // $TEST$ serialization Int + »param4[unresolved]«; + // $TEST$ serialization Int »param1?[0]«; @@ -34,11 +40,17 @@ segment mySegment(param1: List, param2: List?, param3: IntList) { // $TEST$ serialization Int? »param2?[unresolved]«; - // $TEST$ serialization Int + // $TEST$ serialization Int? »param3?[0]«; - // $TEST$ serialization Int + // $TEST$ serialization Int? »param3?[unresolved]«; + + // $TEST$ serialization Int + »param4?[0]«; + + // $TEST$ serialization Int + »param4?[unresolved]«; } class MyClass, MyNullableList sub List?>( diff --git a/packages/safe-ds-lang/tests/resources/typing/expressions/indexed accesses/on maps/main.sdsdev b/packages/safe-ds-lang/tests/resources/typing/expressions/indexed accesses/on maps/main.sdsdev index b55e8e772..b8262736c 100644 --- a/packages/safe-ds-lang/tests/resources/typing/expressions/indexed accesses/on maps/main.sdsdev +++ b/packages/safe-ds-lang/tests/resources/typing/expressions/indexed accesses/on maps/main.sdsdev @@ -2,17 +2,17 @@ package tests.typing.expressions.indexedAccesses.onMaps class MyMap sub Map -segment mySegment(param1: Map, param2: Map?, param3: MyMap) { +segment mySegment(param1: Map, param2: Map, param3: Map?, param4: MyMap) { // $TEST$ serialization Int »param1[""]«; // $TEST$ serialization Int »param1[unresolved]«; - // $TEST$ serialization Int + // $TEST$ serialization Int? »param2[""]«; - // $TEST$ serialization Int + // $TEST$ serialization Int? »param2[unresolved]«; // $TEST$ serialization Int @@ -21,6 +21,12 @@ segment mySegment(param1: Map, param2: Map?, param3: M // $TEST$ serialization Int »param3[unresolved]«; + // $TEST$ serialization Int + »param4[""]«; + + // $TEST$ serialization Int + »param4[unresolved]«; + // $TEST$ serialization Int »param1?[""]«; @@ -34,11 +40,17 @@ segment mySegment(param1: Map, param2: Map?, param3: M // $TEST$ serialization Int? »param2?[unresolved]«; - // $TEST$ serialization Int + // $TEST$ serialization Int? »param3?[""]«; - // $TEST$ serialization Int + // $TEST$ serialization Int? »param3?[unresolved]«; + + // $TEST$ serialization Int + »param4?[""]«; + + // $TEST$ serialization Int + »param4?[unresolved]«; } class MyClass, MyNullableMap sub Map?>( diff --git a/packages/safe-ds-lang/tests/resources/typing/expressions/indexed accesses/on rows/main.sdsdev b/packages/safe-ds-lang/tests/resources/typing/expressions/indexed accesses/on rows/main.sdsdev new file mode 100644 index 000000000..7969ca3a6 --- /dev/null +++ b/packages/safe-ds-lang/tests/resources/typing/expressions/indexed accesses/on rows/main.sdsdev @@ -0,0 +1,85 @@ +package tests.typing.expressions.indexedAccesses.onRows + +class MyRow sub Row + +segment mySegment(param1: Row, param2: Row?, param3: MyRow) { + // $TEST$ serialization Cell + »param1[""]«; + + // $TEST$ serialization Cell + »param1[unresolved]«; + + // $TEST$ serialization Cell + »param2[""]«; + + // $TEST$ serialization Cell + »param2[unresolved]«; + + // $TEST$ serialization Cell + »param3[""]«; + + // $TEST$ serialization Cell + »param3[unresolved]«; + + + // $TEST$ serialization Cell + »param1?[""]«; + + // $TEST$ serialization Cell + »param1?[unresolved]«; + + // $TEST$ serialization Cell? + »param2?[""]«; + + // $TEST$ serialization Cell? + »param2?[unresolved]«; + + // $TEST$ serialization Cell + »param3?[""]«; + + // $TEST$ serialization Cell + »param3?[unresolved]«; +} + +class MyClass( + param1: MyRow, + param2: MyRow?, + param3: MyNullableRow, + + // $TEST$ serialization Cell + p1: Any? = »param1[""]«, + + // $TEST$ serialization Cell + p2: Any? = »param1[unresolved]«, + + // $TEST$ serialization Cell + p3: Any? = »param2[""]«, + + // $TEST$ serialization Cell + p4: Any? = »param2[unresolved]«, + + // $TEST$ serialization Cell + p5: Any? = »param3[""]«, + + // $TEST$ serialization Cell + p6: Any? = »param3[unresolved]«, + + + // $TEST$ serialization Cell + p7: Any? = »param1?[""]«, + + // $TEST$ serialization Cell + p8: Any? = »param1?[unresolved]«, + + // $TEST$ serialization Cell? + p9: Any? = »param2?[""]«, + + // $TEST$ serialization Cell? + p10: Any? = »param2?[unresolved]«, + + // $TEST$ serialization Cell? + p11: Any? = »param3?[""]«, + + // $TEST$ serialization Cell? + p12: Any? = »param3?[unresolved]«, +) diff --git a/packages/safe-ds-lang/tests/resources/validation/types/checking/indexed access on column/main.sdsdev b/packages/safe-ds-lang/tests/resources/validation/types/checking/indexed access on column/main.sdsdev new file mode 100644 index 000000000..bd3aaec80 --- /dev/null +++ b/packages/safe-ds-lang/tests/resources/validation/types/checking/indexed access on column/main.sdsdev @@ -0,0 +1,41 @@ +package tests.validation.types.checking.indexedAccessOnColumn + +@Pure fun column() -> column: Column +@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 + +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( + p1: T, + + // $TEST$ error "Expected type 'Int' but got 'T'." + a: Any? = column()[»p1«], +) diff --git a/packages/safe-ds-lang/tests/resources/validation/types/checking/indexed access on list/main.sdsdev b/packages/safe-ds-lang/tests/resources/validation/types/checking/indexed access on list/main.sdsdev index dd10f8652..e98971844 100644 --- a/packages/safe-ds-lang/tests/resources/validation/types/checking/indexed access on list/main.sdsdev +++ b/packages/safe-ds-lang/tests/resources/validation/types/checking/indexed access on list/main.sdsdev @@ -26,6 +26,21 @@ pipeline myPipeline { unresolved[»""«]; } +class MyList sub List + +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( p1: T, diff --git a/packages/safe-ds-lang/tests/resources/validation/types/checking/indexed access on row/main.sdsdev b/packages/safe-ds-lang/tests/resources/validation/types/checking/indexed access on row/main.sdsdev new file mode 100644 index 000000000..0f7ec433d --- /dev/null +++ b/packages/safe-ds-lang/tests/resources/validation/types/checking/indexed access on row/main.sdsdev @@ -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( + p1: T, + + // $TEST$ error "Expected type 'String' but got 'T'." + a: Any? = row()[»p1«], +) diff --git a/packages/safe-ds-lang/tests/resources/validation/types/checking/indexed access receiver/main.sdsdev b/packages/safe-ds-lang/tests/resources/validation/types/checking/indexed access receiver/main.sdsdev index e7f376656..a3b0b0bde 100644 --- a/packages/safe-ds-lang/tests/resources/validation/types/checking/indexed access receiver/main.sdsdev +++ b/packages/safe-ds-lang/tests/resources/validation/types/checking/indexed access receiver/main.sdsdev @@ -11,25 +11,25 @@ segment mySegment( myList: MyList, myMap: MyMap, ) { - // $TEST$ no error r"Expected type 'List' or 'Map' but got .*\." + // $TEST$ no error r"Indexed access is not defined for type .*\." »[1]«[0]; - // $TEST$ no error r"Expected type 'List' or 'Map' but got .*\." + // $TEST$ no error r"Indexed access is not defined for type .*\." »{0: 1}«[0]; - // $TEST$ error "Expected type 'List' or 'Map' but got 'literal<1>'." + // $TEST$ error "Indexed access is not defined for type 'literal<1>'." »1«[0]; - // $TEST$ no error r"Expected type 'List' or 'Map' but got .*\." + // $TEST$ no error r"Indexed access is not defined for type .*\." »listOrNull«[0]; - // $TEST$ no error r"Expected type 'List' or 'Map' but got .*\." + // $TEST$ no error r"Indexed access is not defined for type .*\." »mapOrNull«[""]; - // $TEST$ error "Expected type 'List' or 'Map' but got 'Int?'." + // $TEST$ error "Indexed access is not defined for type 'Int?'." »intOrNull«[0]; - // $TEST$ no error r"Expected type 'List' or 'Map' but got .*\." + // $TEST$ no error r"Indexed access is not defined for type .*\." »myList«[0]; - // $TEST$ no error r"Expected type 'List' or 'Map' but got .*\." + // $TEST$ no error r"Indexed access is not defined for type .*\." »myMap«[""]; - // $TEST$ error "Expected type 'List' or 'Map' but got 'unknown'." + // $TEST$ error "Indexed access is not defined for type 'unknown'." »unresolved«[0]; } @@ -37,6 +37,6 @@ segment mySegment( class MyClass( p1: T, - // $TEST$ error "Expected type 'List' or 'Map' but got 'T'." + // $TEST$ error "Indexed access is not defined for type 'T'." p2: Any? = »p1«[0], )