Skip to content

Commit

Permalink
Merge pull request #1525 from informalsystems/gabriela/get-only-element
Browse files Browse the repository at this point in the history
Add `getOnlyElement()` built in operator
  • Loading branch information
bugarela authored Oct 22, 2024
2 parents 6463232 + 5a84d1d commit d5d620e
Show file tree
Hide file tree
Showing 8 changed files with 46 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- `quint verify` has the option `--apalache-version` to pull a custom version (#1521)
- Grammar updated with support for an optional leading hashbang (`#!`) line (#1522)
- Added a new operator called `getOnlyElement()` to extract elements out of singleton sets (#1525)

### Changed
### Deprecated
Expand Down
13 changes: 13 additions & 0 deletions docs/pages/docs/builtin.md
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,19 @@ assert(Set(1, 2).allListsUpTo(1) == Set([], [1], [2]))
assert(Set(1).allListsUpTo(2) == Set([], [1], [1, 1]))
```

## getOnlyElement

Signature: `pure def getOnlyElement: (Set[a]) => a`

`s.getOnlyElement()` is, deterministically, the only element of `s`.
If the size of `s` is not 1, this operator has undefined behavior.

### Examples

```quint
assert(Set(5).getOnlyElement() == 5)
```

## chooseSome

Signature: `pure def chooseSome: (Set[a]) => a`
Expand Down
10 changes: 10 additions & 0 deletions quint/src/builtin.qnt
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,16 @@ module builtin {
/// ```
pure def allListsUpTo(s: Set[a], max_length: int): Set[List[a]]

/// `s.getOnlyElement()` is, deterministically, the only element of `s`.
/// If the size of `s` is not 1, this operator has undefined behavior.
///
/// ### Examples
///
/// ```quint
/// assert(Set(5).getOnlyElement() == 5)
/// ```
pure def getOnlyElement(s): (Set[a]) => a

/// `s.chooseSome()` is, deterministically, one element of `s`.
///
/// ### Examples
Expand Down
1 change: 1 addition & 0 deletions quint/src/effects/builtinSignatures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ export const setOperators = [
{ name: 'flatten', effect: standardPropagation(1) },
{ name: 'allLists', effect: standardPropagation(1) },
{ name: 'allListsUpTo', effect: standardPropagation(2) },
{ name: 'getOnlyElement', effect: standardPropagation(1) },
{ name: 'chooseSome', effect: standardPropagation(1) },
{ name: 'oneOf', effect: standardPropagation(1) },
{ name: 'isFinite', effect: standardPropagation(1) },
Expand Down
1 change: 1 addition & 0 deletions quint/src/names/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ export const builtinNames = [
'flatten',
'allLists',
'allListsUpTo',
'getOnlyElement',
'chooseSome',
'oneOf',
'isFinite',
Expand Down
13 changes: 13 additions & 0 deletions quint/src/runtime/impl/builtins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -752,6 +752,19 @@ export function builtinLambda(op: string): (ctx: Context, args: RuntimeValue[])

return right(rv.mkSet(lists.map(list => rv.mkList(list)).toOrderedSet()))
}
case 'getOnlyElement':
// Get the only element of a set, or an error if the set is empty or has more than one element.
return (_, args) => {
const set = args[0].toSet()
if (set.size !== 1) {
return left({
code: 'QNT505',
message: `Called 'getOnlyElement' on a set with ${set.size} elements. Make sure the set has exactly one element.`,
})
}

return right(set.first())
}

case 'q::debug':
// Print a value to the console, and return it
Expand Down
1 change: 1 addition & 0 deletions quint/src/types/builtinSignatures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ const setOperators = [
{ name: 'flatten', type: '(Set[Set[a]]) => Set[a]' },
{ name: 'allLists', type: '(Set[a]) => Set[List[a]]' },
{ name: 'allListsUpTo', type: '(Set[a], int) => Set[List[a]]' },
{ name: 'getOnlyElement', type: '(Set[a]) => a' },
{ name: 'chooseSome', type: '(Set[a]) => a' },
{ name: 'oneOf', type: '(Set[a]) => a' },
{ name: 'isFinite', type: '(Set[a]) => bool' },
Expand Down
6 changes: 6 additions & 0 deletions quint/test/runtime/compile.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -779,6 +779,12 @@ describe('compiling specs to runtime values', () => {
assertResultAsString('Set().allListsUpTo(3)', 'Set(List())')
assertResultAsString('Set(1).allListsUpTo(0)', 'Set(List())')
})

it('getOnlyElement', () => {
assertResultAsString('Set(5).getOnlyElement()', '5')
assertResultAsString('Set().getOnlyElement()', undefined)
assertResultAsString('Set(1, 2).getOnlyElement()', undefined)
})
})

describe('compile over records', () => {
Expand Down

0 comments on commit d5d620e

Please sign in to comment.