Skip to content

Commit e3b2870

Browse files
feat: list & map literals (#619)
Closes #615 Closes #616 ### Summary of Changes Add list and map literals. The computation of the element & key/value types will happen in a later pull request, together with type computation of other type parameters (#23). This PR also removes `vararg` parameters in favor of a parameter with list types. This simplifies the graphical view and gets rid of a bunch of validation rules. --------- Co-authored-by: megalinter-bot <129584137+megalinter-bot@users.noreply.github.com>
1 parent ea4a9ba commit e3b2870

File tree

280 files changed

+783
-2642
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

280 files changed

+783
-2642
lines changed

docs/language/common/parameters.md

+6-41
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@
22

33
_Parameters_ define the expected inputs of some declaration that can be [called][calls]. We refer to such declarations as _callables_. We distinguish between
44

5-
- [required parameters](#required-parameters), which must always be passed,
6-
- [optional parameters](#optional-parameters), which use a default value if no value is passed explicitly, and
7-
- [variadic parameters](#variadic-parameters), which can accept zero or more values.
5+
- [required parameters](#required-parameters), which must always be passed, and
6+
- [optional parameters](#optional-parameters), which use a default value if no value is passed explicitly.
87

98
## Required Parameters
109

@@ -36,21 +35,6 @@ These are the syntactic elements:
3635
- An equals sign.
3736
- The default value of the parameter (here `1`). This must be a constant expression, i.e. something that can be evaluated by the compiler. Particularly [calls][calls] usually do not fulfill this requirement.
3837

39-
## Variadic Parameters
40-
41-
_Variadic parameters_ can consume arbitrarily many [arguments][calls]. Here is an example:
42-
43-
```txt
44-
vararg variadicParameter: Int
45-
```
46-
47-
Let us break down the syntax:
48-
49-
- The keyword `vararg`
50-
- The name of the parameter (here `variadicParameter`). This can be any combination of upper- and lowercase letters, underscores, and numbers, as long as it does not start with a number. However, we suggest to use `lowerCamelCase` for the names of parameters.
51-
- A colon.
52-
- The [type][types] of the parameter (here `Int`).
53-
5438
## Complete Example
5539

5640
Let us now look at a full example of a [segment][segments] called `doSomething` with one [required parameter](#required-parameters) and one [optional parameter](#optional-parameters):
@@ -72,8 +56,6 @@ The interesting part is the list of parameters, which uses the following syntact
7256
Several restrictions apply to the order of parameters and to combinations of the various categories of parameters:
7357

7458
- After an [optional parameter](#optional-parameters) all parameters must be optional.
75-
- A single [variadic parameter](#variadic-parameters) can be added at the end of the parameter list.
76-
- Implied by this: A callable cannot have both [optional parameters](#optional-parameters) and [variadic parameters](#variadic-parameters).
7759

7860
## Corresponding Python Code
7961

@@ -83,7 +65,7 @@ Parameters must be ordered the same way in Python as they are in Safe-DS. Moreov
8365

8466
- Name
8567
- Type
86-
- Kind (required vs. optional vs. variadic)
68+
- Optionality (required vs. optional)
8769
- Default value for optional parameters
8870

8971
Let's look at these elements in turn.
@@ -114,19 +96,17 @@ In this case, the Safe-DS parameters `xPred` and `xTest` refer to the Python par
11496

11597
The Safe-DS type of a parameter should capture the legal values of this parameter accurately. Ideally, the Python parameter should also have a matching [type hint][types-python].
11698

117-
### Matching Kind
99+
### Matching Optionality
118100

119101
Parameters kinds must match on the Safe-DS and Python sides as well. Concretely, this means:
120102

121103
- All required parameters in Safe-DS must be required in Python.
122104
- All optional parameters in Safe-DS must be optional in Python.
123-
- All variadic parameters in Safe-DS must be variadic in Python (`*args`).
124105

125106
Moreover, it must be possible to pass
126107

127-
- required parameters by position,
128-
- optional parameters by name,
129-
- variadic parameters by position.
108+
- required parameters by position, and
109+
- optional parameters by name.
130110

131111
These rules allow us to restrict required parameters to [positional-only][python-positional-only] or optional parameters to [keyword-only][python-keyword-only]. We can also keep both unrestricted.
132112

@@ -162,21 +142,6 @@ def optional(a: int = 1):
162142
fun optional(a: Int = 1)
163143
```
164144

165-
#### Variadic Parameter
166-
167-
```py
168-
# Python code
169-
170-
def variadic(*a: int):
171-
pass
172-
```
173-
174-
```txt
175-
// Safe-DS code
176-
177-
fun variadic(vararg a: Int)
178-
```
179-
180145
### Matching Default Value
181146

182147
Most commonly, default values in Python are literals, since default values are only evaluated once in Python rather than every time the function is called. The following table shows how Safe-DS literals and Python literals correspond:

docs/language/common/variance.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
Variance deals with the question, which generic types are compatible with each other. We explain this concept using the following [class][classes]:
66

77
```txt
8-
class Stack<T>(vararg initialElements: T) {
8+
class Stack<T>(initialElements: List<T>) {
99
fun push(element: T)
1010
1111
fun pop() -> element: T

docs/language/pipeline-language/expressions.md

+7-9
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,6 @@ There are some restriction regarding the choice of positional vs. named argument
219219
- For all [parameters][parameters] of the callee there must be at most one argument.
220220
- For all [required parameters][required-parameters] there must be exactly one argument.
221221
- After a named argument all arguments must be named.
222-
- [Variadic parameters][variadic-parameters] can only be assigned by position.
223222

224223
### Legal Callees
225224

@@ -402,17 +401,17 @@ createValueWrapper()
402401

403402
## Indexed Accesses
404403

405-
An indexed access is currently only used to access one value assigned to a [variadic parameter][variadic-parameters]. In the following example, we use an index access to retrieve the first value that is assigned to the [variadic parameter][variadic-parameters] `values` of the segment `printFirst`:
404+
An indexed access is currently only used to access elements of a list or values of a map by their key. In the following example, we use an index access to retrieve the first element of the `values` list:
406405

407406
```txt
408-
segment printFirst(vararg values: Int) {
407+
segment printFirst(values: List<Int>) {
409408
print(values[0]);
410409
}
411410
```
412411

413412
These are the elements of the syntax:
414413

415-
- The name of the variadic parameter (here `values`).
414+
- An expression that evaluates to a list or map (here the [reference](#references) `values`).
416415
- An opening square bracket.
417416
- The index, which is an expression that evaluates to an integer. The first element has index 0.
418417
- A closing square bracket.
@@ -434,16 +433,16 @@ This is a [class][classes] `LinearRegression`, which has a constructor and an in
434433
We can then use those declarations in a [segment][segments]:
435434

436435
```txt
437-
segment mySegment(vararg regressions: LinearRegression) {
436+
segment mySegment(regressions: List<LinearRegression>) {
438437
regressions[0].drawAsGraph();
439438
}
440439
```
441440

442-
This segment is called `mySegment` and has a [variadic parameter][variadic-parameters] `regressions` of type `LinearRegression`. This means we can pass an arbitrary number of instances of `LinearRegression` to the segment when we [call](#calls) it.
441+
This segment is called `mySegment` and has a [parameter][parameters] `regressions` of type `List<LinearRegression>`.
443442

444443
In the body of the segment we then
445444

446-
1. access the first instance that was pass using an [indexed access](#indexed-accesses),
445+
1. access the first instance in the list using an [indexed access](#indexed-accesses),
447446
2. access the instance method `drawAsGraph` of this instance using a [member access](#member-accesses),
448447
3. [call](#calls) this method.
449448

@@ -456,7 +455,7 @@ class IntList {
456455
fun filter(filterFunction: (element: Int) -> shouldKeep: Boolean) -> filteredList: IntList
457456
}
458457
459-
fun intListOf(vararg elements: Int) -> result: IntList
458+
fun intListOf(elements: List<Int>) -> result: IntList
460459
```
461460

462461
First, we declare a [class][classes] `IntList`, which has a single [method][methods] called `filter`. The `filter` method returns a single result called `filteredList`, which is a new `IntList`. `filteredList` is supposed to only contain the elements of the receiving `IntList` for which the `filterFunction` [parameter][parameters] returns `true`.
@@ -566,7 +565,6 @@ If the default precedence of operators is not sufficient, parentheses can be use
566565
[packages]: ../common/packages.md
567566
[parameters]: ../common/parameters.md
568567
[required-parameters]: ../common/parameters.md#required-parameters
569-
[variadic-parameters]: ../common/parameters.md#variadic-parameters
570568
[results]: ../common/results.md
571569
[types]: ../common/types.md
572570
[callable-types]: ../common/types.md#callable-types

docs/stdlib/safeds_lang.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ The annotation can target these declaration types. If the @Target annotation is
283283

284284
**Parameters:**
285285

286-
* `vararg targets: AnnotationTarget` - The valid targets.
286+
* `targets: List<AnnotationTarget>` - The valid targets.
287287

288288
**Valid targets:**
289289

src/language/builtins/safe-ds-classes.ts

+8-3
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,10 @@ import { SafeDsModuleMembers } from './safe-ds-module-members.js';
55
const CORE_CLASSES_URI = resolveRelativePathToBuiltinFile('safeds/lang/coreClasses.sdsstub');
66

77
export class SafeDsClasses extends SafeDsModuleMembers<SdsClass> {
8-
/* c8 ignore start */
98
get Any(): SdsClass | undefined {
109
return this.getClass('Any');
1110
}
1211

13-
/* c8 ignore stop */
14-
1512
get Boolean(): SdsClass | undefined {
1613
return this.getClass('Boolean');
1714
}
@@ -24,6 +21,14 @@ export class SafeDsClasses extends SafeDsModuleMembers<SdsClass> {
2421
return this.getClass('Int');
2522
}
2623

24+
get List(): SdsClass | undefined {
25+
return this.getClass('List');
26+
}
27+
28+
get Map(): SdsClass | undefined {
29+
return this.getClass('Map');
30+
}
31+
2732
get Nothing(): SdsClass | undefined {
2833
return this.getClass('Nothing');
2934
}

src/language/formatting/safe-ds-formatter.ts

+57-2
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,12 @@ export class SafeDsFormatter extends AbstractFormatter {
142142
this.formatSdsIndexedAccess(node);
143143
} else if (ast.isSdsMemberAccess(node)) {
144144
this.formatSdsMemberAccess(node);
145+
} else if (ast.isSdsList(node)) {
146+
this.formatSdsList(node);
147+
} else if (ast.isSdsMap(node)) {
148+
this.formatSdsMap(node);
149+
} else if (ast.isSdsMapEntry(node)) {
150+
this.formatSdsMapEntry(node);
145151
} else if (ast.isSdsParenthesizedExpression(node)) {
146152
this.formatSdsParenthesizedExpression(node);
147153
} else if (ast.isSdsTemplateStringStart(node)) {
@@ -727,6 +733,55 @@ export class SafeDsFormatter extends AbstractFormatter {
727733
formatter.keyword('.').surround(noSpace());
728734
}
729735

736+
private formatSdsList(node: ast.SdsList) {
737+
const formatter = this.getNodeFormatter(node);
738+
739+
const openingSquareBracket = formatter.keyword('[');
740+
const closingSquareBracket = formatter.keyword(']');
741+
742+
const elements = node.elements;
743+
744+
if (elements.some((it) => this.isComplexExpression(it))) {
745+
formatter.nodes(...elements).prepend(indent());
746+
formatter.keywords(',').prepend(noSpace());
747+
closingSquareBracket.prepend(newLine());
748+
} else {
749+
openingSquareBracket.append(noSpace());
750+
formatter.nodes(...elements.slice(1)).prepend(oneSpace());
751+
formatter.keywords(',').prepend(noSpace());
752+
closingSquareBracket.prepend(noSpace());
753+
}
754+
}
755+
756+
private formatSdsMap(node: ast.SdsMap) {
757+
const formatter = this.getNodeFormatter(node);
758+
759+
const openingCurlyBrace = formatter.keyword('{');
760+
const closingCurlyBrace = formatter.keyword('}');
761+
762+
const entries = node.entries;
763+
764+
if (
765+
entries.length >= 2 ||
766+
entries.some((it) => this.isComplexExpression(it.key) || this.isComplexExpression(it.value))
767+
) {
768+
formatter.nodes(...entries).prepend(indent());
769+
formatter.keywords(',').prepend(noSpace());
770+
closingCurlyBrace.prepend(newLine());
771+
} else {
772+
openingCurlyBrace.append(noSpace());
773+
formatter.nodes(...entries.slice(1)).prepend(oneSpace());
774+
formatter.keywords(',').prepend(noSpace());
775+
closingCurlyBrace.prepend(noSpace());
776+
}
777+
}
778+
779+
private formatSdsMapEntry(node: ast.SdsMapEntry) {
780+
const formatter = this.getNodeFormatter(node);
781+
782+
formatter.keyword(':').prepend(noSpace()).append(oneSpace());
783+
}
784+
730785
private formatSdsParenthesizedExpression(node: ast.SdsParenthesizedExpression): void {
731786
const formatter = this.getNodeFormatter(node);
732787

@@ -754,12 +809,12 @@ export class SafeDsFormatter extends AbstractFormatter {
754809

755810
/**
756811
* Returns whether the expression is considered complex and requires special formatting like placing the associated
757-
* argument on its own line.
812+
* expression on its own line.
758813
*
759814
* @param node The expression to check.
760815
*/
761816
private isComplexExpression(node: ast.SdsExpression | undefined): boolean {
762-
return ast.isSdsChainedExpression(node);
817+
return ast.isSdsChainedExpression(node) || ast.isSdsList(node) || ast.isSdsMap(node);
763818
}
764819

765820
// -----------------------------------------------------------------------------

0 commit comments

Comments
 (0)