strict effects (#18777)

* fixes #17369
* megatest is green for --cpu:arm64
* docgen output includes more tags/raises
* implemented 'effectsOf' 
* algorithm.nim: uses new effectsOf annotation
* closes #18376
* closes #17475
* closes #13905
* allow effectsOf: [a, b]
* added a test case
* parameters that are not ours cannot be declared as .effectsOf
* documentation
* manual: added the 'sort' example
* bootstrap with the new better options
This commit is contained in:
Andreas Rumpf 2021-09-02 12:10:14 +02:00 • committed by GitHub
commit e0ef859130
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
35 changed files with 482 additions and 203 deletions

View file

@ -147,8 +147,13 @@ proc reversed*[T](a: openArray[T], first: Natural, last: int): seq[T]
{.inline, deprecated: "use: `reversed(toOpenArray(a, first, last))`".} =
reversed(toOpenArray(a, first, last))
when defined(nimHasEffectsOf):
{.experimental: "strictEffects".}
else:
{.pragma: effectsOf.}
proc binarySearch*[T, K](a: openArray[T], key: K,
cmp: proc (x: T, y: K): int {.closure.}): int =
cmp: proc (x: T, y: K): int {.closure.}): int {.effectsOf: cmp.} =
## Binary search for `key` in `a`. Return the index of `key` or -1 if not found.
## Assumes that `a` is sorted according to `cmp`.
##
@ -210,7 +215,7 @@ const
onlySafeCode = true
proc lowerBound*[T, K](a: openArray[T], key: K,
cmp: proc(x: T, k: K): int {.closure.}): int =
cmp: proc(x: T, k: K): int {.closure.}): int {.effectsOf: cmp.} =
## Returns the index of the first element in `a` that is not less than
## (i.e. greater or equal to) `key`, or last if no such element is found.
## In other words if you have a sorted sequence and you call
@ -260,7 +265,7 @@ proc lowerBound*[T](a: openArray[T], key: T): int = lowerBound(a, key, cmp[T])
## * `upperBound proc<#upperBound,openArray[T],T>`_
proc upperBound*[T, K](a: openArray[T], key: K,
cmp: proc(x: T, k: K): int {.closure.}): int =
cmp: proc(x: T, k: K): int {.closure.}): int {.effectsOf: cmp.} =
## Returns the index of the first element in `a` that is greater than
## `key`, or last if no such element is found.
## In other words if you have a sorted sequence and you call
@ -318,7 +323,7 @@ template `<-`(a, b) =
copyMem(addr(a), addr(b), sizeof(T))
proc mergeAlt[T](a, b: var openArray[T], lo, m, hi: int,
cmp: proc (x, y: T): int {.closure.}, order: SortOrder) =
cmp: proc (x, y: T): int {.closure.}, order: SortOrder) {.effectsOf: cmp.} =
# Optimization: If max(left) <= min(right) there is nothing to do!
# 1 2 3 4 ## 5 6 7 8
# -> O(n) for sorted arrays.
@ -358,7 +363,7 @@ proc mergeAlt[T](a, b: var openArray[T], lo, m, hi: int,
func sort*[T](a: var openArray[T],
cmp: proc (x, y: T): int {.closure.},
order = SortOrder.Ascending) =
order = SortOrder.Ascending) {.effectsOf: cmp.} =
## Default Nim sort (an implementation of merge sort). The sorting
## is guaranteed to be stable (that is, equal elements stay in the same order)
## and the worst case is guaranteed to be O(n log n).
@ -420,7 +425,7 @@ proc sort*[T](a: var openArray[T], order = SortOrder.Ascending) = sort[T](a,
## * `sortedByIt template<#sortedByIt.t,untyped,untyped>`_
proc sorted*[T](a: openArray[T], cmp: proc(x, y: T): int {.closure.},
order = SortOrder.Ascending): seq[T] =
order = SortOrder.Ascending): seq[T] {.effectsOf: cmp.} =
## Returns `a` sorted by `cmp` in the specified `order`.
##
## **See also:**
@ -497,7 +502,7 @@ template sortedByIt*(seq1, op: untyped): untyped =
func isSorted*[T](a: openArray[T],
cmp: proc(x, y: T): int {.closure.},
order = SortOrder.Ascending): bool =
order = SortOrder.Ascending): bool {.effectsOf: cmp.} =
## Checks to see whether `a` is already sorted in `order`
## using `cmp` for the comparison. The parameters are identical
## to `sort`. Requires O(n) time.
@ -545,7 +550,7 @@ proc isSorted*[T](a: openArray[T], order = SortOrder.Ascending): bool =
proc merge*[T](
result: var seq[T],
x, y: openArray[T], cmp: proc(x, y: T): int {.closure.}
) {.since: (1, 5, 1).} =
) {.since: (1, 5, 1), effectsOf: cmp.} =
## Merges two sorted `openArray`. `x` and `y` are assumed to be sorted.
## If you do not wish to provide your own `cmp`,
## you may use `system.cmp` or instead call the overloaded
@ -638,7 +643,7 @@ proc product*[T](x: openArray[seq[T]]): seq[seq[T]] =
## Produces the Cartesian product of the array.
## Every element of the result is a combination of one element from each seq in `x`,
## with the ith element coming from `x[i]`.
##
##
## .. warning:: complexity may explode.
runnableExamples:
assert product(@[@[1], @[2]]) == @[@[1, 2]]

View file

@ -1333,7 +1333,7 @@ proc now*(): DateTime {.tags: [TimeEffect], benign.} =
## `cpuTime` instead, depending on the use case.
getTime().local
proc dateTime*(year: int, month: Month, monthday: MonthdayRange,
proc dateTime*(year: int, month: Month, monthday: MonthdayRange,
hour: HourRange = 0, minute: MinuteRange = 0, second: SecondRange = 0,
nanosecond: NanosecondRange = 0,
zone: Timezone = local()): DateTime =

View file

@ -8,13 +8,18 @@ import os
when defined(windows):
from strutils import replace
when defined(nimHasEffectsOf):
{.experimental: "strictEffects".}
else:
{.pragma: effectsOf.}
type
PathEntry* = object
kind*: PathComponent
path*: string
iterator walkDirRecFilter*(dir: string, follow: proc(entry: PathEntry): bool = nil,
relative = false, checkDir = true): PathEntry {.tags: [ReadDirEffect].} =
relative = false, checkDir = true): PathEntry {.tags: [ReadDirEffect], effectsOf: follow.} =
## Improved `os.walkDirRec`.
#[
note: a yieldFilter isn't needed because caller can filter at call site, without

View file

@ -78,7 +78,7 @@ proc c_fputs(c: cstring, f: File): cint {.
proc c_fgets(c: cstring, n: cint, f: File): cstring {.
importc: "fgets", header: "<stdio.h>", tags: [ReadIOEffect].}
proc c_fgetc(stream: File): cint {.
importc: "fgetc", header: "<stdio.h>", tags: [ReadIOEffect].}
importc: "fgetc", header: "<stdio.h>", tags: [].}
proc c_ungetc(c: cint, f: File): cint {.
importc: "ungetc", header: "<stdio.h>", tags: [].}
proc c_putc(c: cint, stream: File): cint {.

View file

@ -261,7 +261,7 @@ proc cpDir*(`from`, to: string) {.raises: [OSError].} =
checkOsError()
proc exec*(command: string) {.
raises: [OSError], tags: [ExecIOEffect].} =
raises: [OSError], tags: [ExecIOEffect, WriteIOEffect].} =
## Executes an external process. If the external process terminates with
## a non-zero exit code, an OSError exception is raised.
##
@ -274,7 +274,7 @@ proc exec*(command: string) {.
checkOsError()
proc exec*(command: string, input: string, cache = "") {.
raises: [OSError], tags: [ExecIOEffect].} =
raises: [OSError], tags: [ExecIOEffect, WriteIOEffect].} =
## Executes an external process. If the external process terminates with
## a non-zero exit code, an OSError exception is raised.
log "exec: " & command:
@ -284,7 +284,7 @@ proc exec*(command: string, input: string, cache = "") {.
echo output
proc selfExec*(command: string) {.
raises: [OSError], tags: [ExecIOEffect].} =
raises: [OSError], tags: [ExecIOEffect, WriteIOEffect].} =
## Executes an external command with the current nim/nimble executable.
## `Command` must not contain the "nim " part.
let c = selfExe() & " " & command