.forbids pragma: defining forbidden tags (#20050)

* .forbids pragma: defining illegal effects for proc types

This patch intends to define the opposite of the .tags pragma: a way to define effects which are not allowed in a proc.

* updated documentation and changelogs for the forbids pragma

* renamed notTagEffects to forbiddenEffects

* corrected issues of forbids pragma

the forbids pragma didn't handle simple restrictions properly and it also had issues with subtyping

* removed incorrect character from changelog

* added test to cover the interaction of methods and the forbids pragma

* covering the interaction of the tags and forbids pragmas

* updated manual about the forbids pragma

* removed useless statement

* corrected the subtyping of proc types using the forbids pragma

* updated manual for the forbids pragma

* updated documentations for forbids pragma

* updated nim docs

* updated docs with rsttester.nim

* regenerated documentation

* updated rst docs

* Update changelog.md

Co-authored-by: ringabout <43030857+ringabout@users.noreply.github.com>

* updated changelog

* corrected typo

Co-authored-by: ringabout <43030857+ringabout@users.noreply.github.com>
This commit is contained in:
Lancer11211 2022-07-26 07:40:49 +02:00 • committed by GitHub
commit efd5c571bf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 405 additions and 71 deletions

View file

@ -0,0 +1,21 @@
discard """
action: compile
errormsg: "type mismatch: got <proc (x: int){.gcsafe, locks: 0.}>"
line: 21
"""
type
Effect1 = object
Effect2 = object
Effect3 = object
proc test(fnc: proc(x: int): void {.forbids: [Effect2].}) {.tags: [Effect1, Effect3, RootEffect].} =
fnc(1)
proc t1(x: int): void = echo $x
proc t2(x: int): void {.tags: [Effect2].} = echo $x
proc t3(x: int): void {.tags: [Effect3].} = echo $x
test(t1)
test(t3)
test(t2)

View file

@ -0,0 +1,52 @@
discard """
action: compile
"""
import std/locks
type
Test2Effect* = object
Test2* = object
value2*: int
Test1Effect* = object
Test1* = object
value1*: int
Main* = object
test1Lock: Lock
test1: Test1
test2Lock: Lock
test2: Test2
proc `=copy`(obj1: var Test2, obj2: Test2) {.error.}
proc `=copy`(obj1: var Test1, obj2: Test1) {.error.}
proc `=copy`(obj1: var Main, obj2: Main) {.error.}
proc withTest1(main: var Main,
fn: proc(test1: var Test1) {.gcsafe, forbids: [Test1Effect].}) {.gcsafe, tags: [Test1Effect, RootEffect].} =
withLock(main.test1Lock):
fn(main.test1)
proc withTest2(main: var Main,
fn: proc(test1: var Test2) {.gcsafe, forbids: [Test2Effect].}) {.gcsafe, tags: [Test2Effect, RootEffect].} =
withLock(main.test2Lock):
fn(main.test2)
proc newMain(): Main =
var test1lock: Lock
initLock(test1Lock)
var test2lock: Lock
initLock(test2Lock)
var main = Main(test1Lock: move(test1Lock), test1: Test1(value1: 1),
test2Lock: move(test2Lock), test2: Test2(value2: 2))
main.withTest1(proc(test1: var Test1) = test1.value1 += 1)
main.withTest2(proc(test2: var Test2) = test2.value2 += 1)
move main
var main = newMain()
main.withTest1(proc(test1: var Test1) =
test1.value1 += 1
main.withTest2(proc(test2: var Test2) = test2.value2 += 1)
)
main.withTest1(proc(test1: var Test1) {.tags: [].} = echo $test1.value1)
main.withTest2(proc(test2: var Test2) {.tags: [].} = echo $test2.value2)

View file

@ -0,0 +1,19 @@
discard """
action: compile
errormsg: "writeSomething() has an illegal effect: WriteIO"
line: 19
"""
type
IO = object of RootEffect ## input/output effect
ReadIO = object of IO ## input effect
WriteIO = object of IO ## output effect
proc readSomething(): string {.tags: [ReadIO].} = ""
proc writeSomething(): void {.tags: [WriteIO].} = echo "..."
proc noWritesPlease() {.forbids: [WriteIO].} =
# this is OK:
echo readSomething()
# the compiler prevents this:
writeSomething()

View file

@ -0,0 +1,15 @@
discard """
action: compile
errormsg: "func1() has an illegal effect: IO"
line: 15
"""
type IO = object ## input/output effect
proc func1(): string {.tags: [IO].} = discard
proc func2(): string = discard
proc no_IO_please() {.forbids: [IO].} =
# this is OK because it didn't define any tag:
discard func2()
# the compiler prevents this:
let y = func1()

View file

@ -0,0 +1,18 @@
discard """
action: compile
errormsg: "method1(c) has an illegal effect: IO"
line: 18
"""
type
IO = object ## input/output effect
CustomObject* = object of RootObj
text: string
method method1(obj: var CustomObject): string {.tags: [IO].} = obj.text & "."
method method2(obj: var CustomObject): string = obj.text & ":"
proc noIO() {.forbids: [IO].} =
var c = CustomObject(text: "a")
echo c.method2()
echo c.method1()

View file

@ -0,0 +1,20 @@
discard """
action: compile
errormsg: "writeSomething(\"a\") can have an unlisted effect: WriteIO"
line: 20
"""
type
IO = object of RootEffect ## input/output effect
ReadIO = object of IO ## input effect
WriteIO = object of IO ## output effect
LogIO = object of IO ## another output effect
proc readSomething(): string {.tags: [ReadIO].} = ""
proc writeSomething(msg: string): void {.tags: [WriteIO].} = echo msg
proc logSomething(msg: string): void {.tags: [LogIo].} = echo msg
proc noWritesPlease() {.forbids: [WriteIO], tags: [LogIO, ReadIO].} =
echo readSomething()
logSomething("a")
writeSomething("a")

View file

@ -0,0 +1,17 @@
discard """
action: compile
errormsg: "writeSomething(\"a\") has an illegal effect: WriteIO"
line: 17
"""
type
IO = object of RootEffect ## input/output effect
ReadIO = object of IO ## input effect
WriteIO = object of IO ## output effect
proc readSomething(): string {.tags: [ReadIO].} = ""
proc writeSomething(msg: string): void {.tags: [WriteIO].} = echo msg
proc illegalEffectNegation() {.forbids: [WriteIO], tags: [ReadIO, WriteIO].} =
echo readSomething()
writeSomething("a")

View file

@ -0,0 +1,19 @@
discard """
action: compile
errormsg: "type mismatch: got <ProcType2>"
line: 19
"""
type MyEffect = object
type ProcType1 = proc (i: int): void {.forbids: [MyEffect].}
type ProcType2 = proc (i: int): void
proc testFunc(p: ProcType1): void = p(1)
proc toBeCalled(i: int): void {.tags: [MyEffect].} = echo $i
let emptyTags = proc(i: int): void {.tags: [].} = echo $i
let noTags: ProcType2 = proc(i: int): void = toBeCalled(i)
testFunc(emptyTags)
testFunc(noTags)

View file

@ -0,0 +1,23 @@
discard """
action: compile
errormsg: "type mismatch: got <proc (i: int){.gcsafe, locks: 0.}>"
line: 23
"""
type MyEffect = object
type ProcType1 = proc (i: int): void {.forbids: [MyEffect].}
type ProcType2 = proc (i: int): void
proc caller1(p: ProcType1): void = p(1)
proc caller2(p: ProcType2): void = p(1)
proc effectful(i: int): void {.tags: [MyEffect].} = echo $i
proc effectless(i: int): void {.forbids: [MyEffect].} = echo $i
proc toBeCalled1(i: int): void = effectful(i)
proc toBeCalled2(i: int): void = effectless(i)
caller1(toBeCalled2)
caller2(toBeCalled1)
caller2(toBeCalled2)
caller1(toBeCalled1)