Remove Covariance section from the experimental manual (#18688)

* Remove `Covariance` section

* Add blank lines after `.. code-block::`

* Fix CI?
This commit is contained in:
konsumlamm 2021-08-29 10:42:52 +02:00 • committed by GitHub
commit 0ddd9519c0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -70,6 +70,7 @@ type `void` are treated as non-existent, `void` as a return type means that
the procedure does not return a value: the procedure does not return a value:
.. code-block:: nim .. code-block:: nim
proc nothing(x, y: void): void = proc nothing(x, y: void): void =
echo "ha" echo "ha"
@ -78,6 +79,7 @@ the procedure does not return a value:
The `void` type is particularly useful for generic code: The `void` type is particularly useful for generic code:
.. code-block:: nim .. code-block:: nim
proc callProc[T](p: proc (x: T), x: T) = proc callProc[T](p: proc (x: T), x: T) =
when T is void: when T is void:
p() p()
@ -93,6 +95,7 @@ The `void` type is particularly useful for generic code:
However, a `void` type cannot be inferred in generic code: However, a `void` type cannot be inferred in generic code:
.. code-block:: nim .. code-block:: nim
callProc(emptyProc) callProc(emptyProc)
# Error: type mismatch: got (proc ()) # Error: type mismatch: got (proc ())
# but expected one of: # but expected one of:
@ -102,106 +105,6 @@ The `void` type is only valid for parameters and return types; other symbols
cannot have the type `void`. cannot have the type `void`.
Covariance
==========
Covariance in Nim can be introduced only through pointer-like types such
as `ptr` and `ref`. Sequence, Array and OpenArray types, instantiated
with pointer-like types will be considered covariant if and only if they
are also immutable. The introduction of a `var` modifier or additional
`ptr` or `ref` indirections would result in invariant treatment of
these types.
`proc` types are currently always invariant, but future versions of Nim
may relax this rule.
User-defined generic types may also be covariant with respect to some of
their parameters. By default, all generic params are considered invariant,
but you may choose the apply the prefix modifier `in` to a parameter to
make it contravariant or `out` to make it covariant:
.. code-block:: nim
type
AnnotatedPtr[out T] =
metadata: MyTypeInfo
p: ref T
RingBuffer[out T] =
startPos: int
data: seq[T]
Action {.importcpp: "std::function<void ('0)>".} [in T] = object
When the designated generic parameter is used to instantiate a pointer-like
type as in the case of `AnnotatedPtr` above, the resulting generic type will
also have pointer-like covariance:
.. code-block:: nim
type
GuiWidget = object of RootObj
Button = object of GuiWidget
ComboBox = object of GuiWidget
var
widgetPtr: AnnotatedPtr[GuiWidget]
buttonPtr: AnnotatedPtr[Button]
...
proc drawWidget[T](x: AnnotatedPtr[GuiWidget]) = ...
# you can call procs expecting base types by supplying a derived type
drawWidget(buttonPtr)
# and you can convert more-specific pointer types to more general ones
widgetPtr = buttonPtr
Just like with regular pointers, covariance will be enabled only for immutable
values:
.. code-block:: nim
proc makeComboBox[T](x: var AnnotatedPtr[GuiWidget]) =
x.p = new(ComboBox)
makeComboBox(buttonPtr) # Error, AnnotatedPtr[Button] cannot be modified
# to point to a ComboBox
On the other hand, in the `RingBuffer` example above, the designated generic
param is used to instantiate the non-pointer `seq` type, which means that
the resulting generic type will have covariance that mimics an array or
sequence (i.e. it will be covariant only when instantiated with `ptr` and
`ref` types):
.. code-block:: nim
type
Base = object of RootObj
Derived = object of Base
proc consumeBaseValues(b: RingBuffer[Base]) = ...
var derivedValues: RingBuffer[Derived]
consumeBaseValues(derivedValues) # Error, Base and Derived values may differ
# in size
proc consumeBasePointers(b: RingBuffer[ptr Base]) = ...
var derivedPointers: RingBuffer[ptr Derived]
consumeBaseValues(derivedPointers) # This is legal
Please note that Nim will treat the user-defined pointer-like types as
proper alternatives to the built-in pointer types. That is, types such
as `seq[AnnotatedPtr[T]]` or `RingBuffer[AnnotatedPtr[T]]` will also be
considered covariant and you can create new pointer-like types by instantiating
other user-defined pointer-like types.
The contravariant parameters introduced with the `in` modifier are currently
useful only when interfacing with imported types having such semantics.
Automatic dereferencing Automatic dereferencing
======================= =======================
@ -209,6 +112,7 @@ Automatic dereferencing is performed for the first argument of a routine call.
This feature has to be enabled via `{.experimental: "implicitDeref".}`: This feature has to be enabled via `{.experimental: "implicitDeref".}`:
.. code-block:: nim .. code-block:: nim
{.experimental: "implicitDeref".} {.experimental: "implicitDeref".}
proc depth(x: NodeObj): int = ... proc depth(x: NodeObj): int = ...
@ -219,6 +123,7 @@ This feature has to be enabled via `{.experimental: "implicitDeref".}`:
echo n.depth echo n.depth
# no need to write n[].depth either # no need to write n[].depth either
Code reordering Code reordering
=============== ===============
@ -281,6 +186,7 @@ statement) can have their entire initialization statement reordered. Be wary of
what code is executed at the top level: what code is executed at the top level:
.. code-block:: nim .. code-block:: nim
{.experimental: "codeReordering".} {.experimental: "codeReordering".}
proc a() = proc a() =
@ -300,6 +206,7 @@ what code is executed at the top level:
code is the same as it would be with code reordering disabled. code is the same as it would be with code reordering disabled.
.. code-block:: nim .. code-block:: nim
{.experimental: "codeReordering".} {.experimental: "codeReordering".}
proc x() = proc x() =
@ -313,6 +220,7 @@ It is important to note that reordering *only* works for symbols at top level
scope. Therefore, the following will *fail to compile:* scope. Therefore, the following will *fail to compile:*
.. code-block:: nim .. code-block:: nim
{.experimental: "codeReordering".} {.experimental: "codeReordering".}
proc a() = proc a() =
@ -331,6 +239,7 @@ has different names. This does not need an `experimental` switch, but is an
unstable feature. unstable feature.
.. code-block:: Nim .. code-block:: Nim
proc foo(x: int) = proc foo(x: int) =
echo "Using x: ", x echo "Using x: ", x
proc foo(y: int) = proc foo(y: int) =
@ -349,6 +258,7 @@ As a special more convenient notation, proc expressions involved in procedure
calls can use the `do` keyword: calls can use the `do` keyword:
.. code-block:: nim .. code-block:: nim
sort(cities) do (x,y: string) -> int: sort(cities) do (x,y: string) -> int:
cmp(x.len, y.len) cmp(x.len, y.len)
@ -371,6 +281,7 @@ parentheses is just a block of code. The `do` notation can be used to
pass multiple blocks to a macro: pass multiple blocks to a macro:
.. code-block:: nim .. code-block:: nim
macro performWithUndo(task, undo: untyped) = ... macro performWithUndo(task, undo: untyped) = ...
performWithUndo do: performWithUndo do:
@ -380,7 +291,6 @@ pass multiple blocks to a macro:
# code to undo it # code to undo it
Special Operators Special Operators
================= =================
@ -404,6 +314,7 @@ the expression, where the unknown field or proc name is passed to
an `untyped` parameter: an `untyped` parameter:
.. code-block:: nim .. code-block:: nim
a.b # becomes `.`(a, b) a.b # becomes `.`(a, b)
a.b(c, d) # becomes `.`(a, b, c, d) a.b(c, d) # becomes `.`(a, b, c, d)
@ -411,6 +322,7 @@ The matched dot operators can be symbols of any callable kind (procs,
templates and macros), depending on the desired effect: templates and macros), depending on the desired effect:
.. code-block:: nim .. code-block:: nim
template `.`(js: PJsonNode, field: untyped): JSON = js[astToStr(field)] template `.`(js: PJsonNode, field: untyped): JSON = js[astToStr(field)]
var js = parseJson("{ x: 1, y: 2}") var js = parseJson("{ x: 1, y: 2}")
@ -435,6 +347,7 @@ operator `.=`
This operator will be matched against assignments to missing fields. This operator will be matched against assignments to missing fields.
.. code-block:: nim .. code-block:: nim
a.b = c # becomes `.=`(a, b, c) a.b = c # becomes `.=`(a, b, c)
Call operator Call operator
@ -445,6 +358,7 @@ for existing routines. The experimental `callOperator` switch must be enabled
to use this operator. to use this operator.
.. code-block:: nim .. code-block:: nim
{.experimental: "callOperator".} {.experimental: "callOperator".}
template `()`(a: int, b: float): untyped = $(a, b) template `()`(a: int, b: float): untyped = $(a, b)
@ -480,6 +394,7 @@ All types for which `nil` is a valid value can be annotated with the
`not nil` annotation to exclude `nil` as a valid value: `not nil` annotation to exclude `nil` as a valid value:
.. code-block:: nim .. code-block:: nim
{.experimental: "notnil".} {.experimental: "notnil".}
type type
@ -502,6 +417,7 @@ here.
.. include:: manual_experimental_strictnotnil.rst .. include:: manual_experimental_strictnotnil.rst
Concepts Concepts
======== ========
@ -511,6 +427,7 @@ arbitrary set of requirements that the matched type must satisfy.
Concepts are written in the following form: Concepts are written in the following form:
.. code-block:: nim .. code-block:: nim
type type
Comparable = concept x, y Comparable = concept x, y
(x < y) is bool (x < y) is bool
@ -536,6 +453,7 @@ instance. You can also apply the `type` modifier to create a named instance of
the type itself: the type itself:
.. code-block:: nim .. code-block:: nim
type type
MyConcept = concept x, var v, ref r, ptr p, static s, type T MyConcept = concept x, var v, ref r, ptr p, static s, type T
... ...
@ -545,6 +463,7 @@ and parameters are expected. This provides a more convenient way to check for
the presence of callable symbols with specific signatures: the presence of callable symbols with specific signatures:
.. code-block:: nim .. code-block:: nim
type type
OutputStream = concept var s OutputStream = concept var s
s.write(string) s.write(string)
@ -555,6 +474,7 @@ type, following the `concept` keyword is also considered to have the
explicit modifier and will be matched only as a type. explicit modifier and will be matched only as a type.
.. code-block:: nim .. code-block:: nim
type type
# Let's imagine a user-defined casting framework with operators # Let's imagine a user-defined casting framework with operators
# such as `val.to(string)` and `val.to(JSonValue)`. We can test # such as `val.to(string)` and `val.to(JSonValue)`. We can test
@ -592,6 +512,7 @@ concept and, as a result, a wrong overload is selected, you can apply the
`explain` pragma to either the concept body or a particular call-site. `explain` pragma to either the concept body or a particular call-site.
.. code-block:: nim .. code-block:: nim
type type
MyConcept {.explain.} = concept ... MyConcept {.explain.} = concept ...
@ -607,6 +528,7 @@ Generic concepts and type binding rules
The concept types can be parametric just like the regular generic types: The concept types can be parametric just like the regular generic types:
.. code-block:: nim .. code-block:: nim
### matrixalgo.nim ### matrixalgo.nim
import std/typetraits import std/typetraits
@ -680,6 +602,7 @@ Unbound static params will be inferred from expressions involving the `==`
operator and also when types dependent on them are being matched: operator and also when types dependent on them are being matched:
.. code-block:: nim .. code-block:: nim
type type
MatrixReducer[M, N: static int; T] = concept x MatrixReducer[M, N: static int; T] = concept x
x.reduce(SquareMatrix[N, T]) is array[M, int] x.reduce(SquareMatrix[N, T]) is array[M, int]
@ -694,6 +617,7 @@ matched without permanently inferring it. This may be useful when you need
to match several procs accepting the same wide class of types: to match several procs accepting the same wide class of types:
.. code-block:: nim .. code-block:: nim
type type
Enumerable[T] = concept e Enumerable[T] = concept e
for v in e: for v in e:
@ -716,6 +640,7 @@ types used in multiple signatures, without actually requiring any concrete
types, thus allowing you to encode implementation-defined types: types, thus allowing you to encode implementation-defined types:
.. code-block:: nim .. code-block:: nim
type type
MyConcept = concept x MyConcept = concept x
type T1 = auto type T1 = auto
@ -776,6 +701,7 @@ accessible through the dot operator in procs where the concept was successfully
matched to a concrete type: matched to a concrete type:
.. code-block:: nim .. code-block:: nim
type type
DateTime = concept t1, t2, type T DateTime = concept t1, t2, type T
const Min = T.MinDate const Min = T.MinDate
@ -810,6 +736,7 @@ one. As an alternative way of defining concept refinements, you can use the
object inheritance syntax involving the `of` keyword: object inheritance syntax involving the `of` keyword:
.. code-block:: nim .. code-block:: nim
type type
Graph = concept g, type G of EquallyComparable, Copyable Graph = concept g, type G of EquallyComparable, Copyable
type type
@ -853,6 +780,7 @@ object inheritance syntax involving the `of` keyword:
the concept body: the concept body:
.. code-block:: nim .. code-block:: nim
type type
Stringable = concept x Stringable = concept x
$x is string $x is string
@ -904,6 +832,7 @@ object inheritance syntax involving the `of` keyword:
types to the corresponding VTable type. types to the corresponding VTable type.
.. code-block:: nim .. code-block:: nim
type type
IntEnumerable = vtref Enumerable[int] IntEnumerable = vtref Enumerable[int]
@ -970,6 +899,7 @@ language may weaken this restriction.)
The signature has to be: The signature has to be:
.. code-block:: nim .. code-block:: nim
proc `=deepCopy`(x: T): T proc `=deepCopy`(x: T): T
This mechanism will be used by most data structures that support shared memory This mechanism will be used by most data structures that support shared memory
@ -1037,6 +967,7 @@ phase of the compiler: This means they provide an easy way to enhance the
compilation pipeline with user defined optimizations: compilation pipeline with user defined optimizations:
.. code-block:: nim .. code-block:: nim
template optMul{`*`(a, 2)}(a: int): int = a+a template optMul{`*`(a, 2)}(a: int): int = a+a
let x = 3 let x = 3
@ -1059,6 +990,7 @@ Unfortunately optimizations are hard to get right and even the tiny example
is **wrong**: is **wrong**:
.. code-block:: nim .. code-block:: nim
template optMul{`*`(a, 2)}(a: int): int = a+a template optMul{`*`(a, 2)}(a: int): int = a+a
proc f(): int = proc f(): int =
@ -1071,6 +1003,7 @@ We cannot duplicate 'a' if it denotes an expression that has a side effect!
Fortunately Nim supports side effect analysis: Fortunately Nim supports side effect analysis:
.. code-block:: nim .. code-block:: nim
template optMul{`*`(a, 2)}(a: int{noSideEffect}): int = a+a template optMul{`*`(a, 2)}(a: int{noSideEffect}): int = a+a
proc f(): int = proc f(): int =
@ -1088,11 +1021,13 @@ cannot really do that however as the following code only swaps arguments
blindly: blindly:
.. code-block:: nim .. code-block:: nim
template mulIsCommutative{`*`(a, b)}(a, b: int): int = b*a template mulIsCommutative{`*`(a, b)}(a, b: int): int = b*a
What optimizers really need to do is a *canonicalization*: What optimizers really need to do is a *canonicalization*:
.. code-block:: nim .. code-block:: nim
template canonMul{`*`(a, b)}(a: int{lit}, b: int): int = b*a template canonMul{`*`(a, b)}(a: int{lit}, b: int): int = b*a
The `int{lit}` parameter pattern matches against an expression of The `int{lit}` parameter pattern matches against an expression of
@ -1152,6 +1087,7 @@ but also to every other bound parameter; syntactically they need to occur after
the ordinary AST predicates: the ordinary AST predicates:
.. code-block:: nim .. code-block:: nim
template ex{a = b + c}(a: int{noalias}, b, c: int) = template ex{a = b + c}(a: int{noalias}, b, c: int) =
# this transformation is only valid if 'b' and 'c' do not alias 'a': # this transformation is only valid if 'b' and 'c' do not alias 'a':
a = b a = b
@ -1160,6 +1096,7 @@ the ordinary AST predicates:
Another example: Another example:
.. code-block:: nim .. code-block:: nim
proc somefunc(s: string) = assert s == "variable" proc somefunc(s: string) = assert s == "variable"
proc somefunc(s: string{nkStrLit}) = assert s == "literal" proc somefunc(s: string{nkStrLit}) = assert s == "literal"
proc somefunc(s: string{nkRStrLit}) = assert s == r"raw" proc somefunc(s: string{nkRStrLit}) = assert s == r"raw"
@ -1189,6 +1126,7 @@ The `|` operator
The `|` operator if used as infix operator creates an ordered choice: The `|` operator if used as infix operator creates an ordered choice:
.. code-block:: nim .. code-block:: nim
template t{0|1}(): untyped = 3 template t{0|1}(): untyped = 3
let a = 1 let a = 1
# outputs 3: # outputs 3:
@ -1198,6 +1136,7 @@ The matching is performed after the compiler performed some optimizations like
constant folding, so the following does not work: constant folding, so the following does not work:
.. code-block:: nim .. code-block:: nim
template t{0|1}(): untyped = 3 template t{0|1}(): untyped = 3
# outputs 1: # outputs 1:
echo 1 echo 1
@ -1215,6 +1154,7 @@ A pattern expression can be bound to a pattern parameter via the `expr{param}`
notation: notation:
.. code-block:: nim .. code-block:: nim
template t{(0|1|2){x}}(x: untyped): untyped = x+1 template t{(0|1|2){x}}(x: untyped): untyped = x+1
let a = 1 let a = 1
# outputs 2: # outputs 2:
@ -1227,6 +1167,7 @@ The `~` operator
The `~` operator is the **not** operator in patterns: The `~` operator is the **not** operator in patterns:
.. code-block:: nim .. code-block:: nim
template t{x = (~x){y} and (~x){z}}(x, y, z: bool) = template t{x = (~x){y} and (~x){z}}(x, y, z: bool) =
x = y x = y
if x: x = z if x: x = z
@ -1246,6 +1187,7 @@ The `*` operator can *flatten* a nested binary expression like `a & b & c`
to `&(a, b, c)`: to `&(a, b, c)`:
.. code-block:: nim .. code-block:: nim
var var
calls = 0 calls = 0
@ -1270,6 +1212,7 @@ which is flattened into a call expression; thus the invocation of `optConc`
produces: produces:
.. code-block:: nim .. code-block:: nim
`&&`("my", space & "awe", "some ", "concat") `&&`("my", space & "awe", "some ", "concat")
@ -1280,6 +1223,7 @@ The `**` is much like the `*` operator, except that it gathers not only
all the arguments, but also the matched operators in reverse polish notation: all the arguments, but also the matched operators in reverse polish notation:
.. code-block:: nim .. code-block:: nim
import std/macros import std/macros
type type
@ -1324,6 +1268,7 @@ parameter is of the type `varargs` it is treated specially and it can match
0 or more arguments in the AST to be matched against: 0 or more arguments in the AST to be matched against:
.. code-block:: nim .. code-block:: nim
template optWrite{ template optWrite{
write(f, x) write(f, x)
((write|writeLine){w})(f, y) ((write|writeLine){w})(f, y)
@ -1339,6 +1284,7 @@ The following example shows how some simple partial evaluation can be
implemented with term rewriting: implemented with term rewriting:
.. code-block:: nim .. code-block:: nim
proc p(x, y: int; cond: bool): int = proc p(x, y: int; cond: bool): int =
result = if cond: x + y else: x - y result = if cond: x + y else: x - y
@ -1352,6 +1298,7 @@ Example: Hoisting
The following example shows how some form of hoisting can be implemented: The following example shows how some form of hoisting can be implemented:
.. code-block:: nim .. code-block:: nim
import std/pegs import std/pegs
template optPeg{peg(pattern)}(pattern: string{lit}): Peg = template optPeg{peg(pattern)}(pattern: string{lit}): Peg =
@ -1375,6 +1322,7 @@ Parameter constraints can also be used for ordinary routine parameters; these
constraints affect ordinary overloading resolution then: constraints affect ordinary overloading resolution then:
.. code-block:: nim .. code-block:: nim
proc optLit(a: string{lit|`const`}) = proc optLit(a: string{lit|`const`}) =
echo "string literal" echo "string literal"
proc optLit(a: string) = proc optLit(a: string) =
@ -1426,6 +1374,7 @@ Spawn statement
`spawn`:idx: can be used to pass a task to the thread pool: `spawn`:idx: can be used to pass a task to the thread pool:
.. code-block:: nim .. code-block:: nim
import std/threadpool import std/threadpool
proc processLine(line: string) = proc processLine(line: string) =
@ -1458,6 +1407,7 @@ with the `^` operator is **blocking**. However, one can use `blockUntilAny` to
wait on multiple flow variables at the same time: wait on multiple flow variables at the same time:
.. code-block:: nim .. code-block:: nim
import std/threadpool, ... import std/threadpool, ...
# wait until 2 out of 3 servers received the update: # wait until 2 out of 3 servers received the update:
@ -1552,6 +1502,7 @@ Protecting global variables
Object fields and global variables can be annotated via a `guard` pragma: Object fields and global variables can be annotated via a `guard` pragma:
.. code-block:: nim .. code-block:: nim
var glock: TLock var glock: TLock
var gdata {.guard: glock.}: int var gdata {.guard: glock.}: int
@ -1559,6 +1510,7 @@ The compiler then ensures that every access of `gdata` is within a `locks`
section: section:
.. code-block:: nim .. code-block:: nim
proc invalid = proc invalid =
# invalid: unguarded access: # invalid: unguarded access:
echo gdata echo gdata
@ -1577,6 +1529,7 @@ semantics and should not be used directly! It should only be used in templates
that also implement some form of locking at runtime: that also implement some form of locking at runtime:
.. code-block:: nim .. code-block:: nim
template lock(a: TLock; body: untyped) = template lock(a: TLock; body: untyped) =
pthread_mutex_lock(a) pthread_mutex_lock(a)
{.locks: [a].}: {.locks: [a].}:
@ -1590,6 +1543,7 @@ The guard does not need to be of any particular type. It is flexible enough to
model low level lockfree mechanisms: model low level lockfree mechanisms:
.. code-block:: nim .. code-block:: nim
var dummyLock {.compileTime.}: int var dummyLock {.compileTime.}: int
var atomicCounter {.guard: dummyLock.}: int var atomicCounter {.guard: dummyLock.}: int
@ -1617,6 +1571,7 @@ Since objects can reside on the heap or on the stack this greatly enhances the
expressivity of the language: expressivity of the language:
.. code-block:: nim .. code-block:: nim
type type
ProtectedCounter = object ProtectedCounter = object
v {.guard: L.}: int v {.guard: L.}: int
@ -1631,6 +1586,7 @@ The access to field `x.v` is allowed since its guard `x.L` is active.
After template expansion, this amounts to: After template expansion, this amounts to:
.. code-block:: nim .. code-block:: nim
proc incCounters(counters: var openArray[ProtectedCounter]) = proc incCounters(counters: var openArray[ProtectedCounter]) =
for i in 0..counters.high: for i in 0..counters.high:
pthread_mutex_lock(counters[i].L) pthread_mutex_lock(counters[i].L)
@ -1651,6 +1607,7 @@ Two paths are considered equivalent if they are syntactically the same.
This means the following compiles (for now) even though it really should not: This means the following compiles (for now) even though it really should not:
.. code-block:: nim .. code-block:: nim
{.locks: [a[i].L].}: {.locks: [a[i].L].}:
inc i inc i
access a[i].v access a[i].v
@ -1671,6 +1628,7 @@ of the same level can only be acquired *at the same time* within a
single `locks` section: single `locks` section:
.. code-block:: nim .. code-block:: nim
var a, b: TLock[2] var a, b: TLock[2]
var x: TLock[1] var x: TLock[1]
# invalid locking order: TLock[1] cannot be acquired before TLock[2]: # invalid locking order: TLock[1] cannot be acquired before TLock[2]:
@ -1697,6 +1655,7 @@ the runtime check is required to ensure a global ordering for two locks `a`
and `b` of the same lock level: and `b` of the same lock level:
.. code-block:: nim .. code-block:: nim
template multilock(a, b: ptr TLock; body: untyped) = template multilock(a, b: ptr TLock; body: untyped) =
if cast[ByteAddress](a) < cast[ByteAddress](b): if cast[ByteAddress](a) < cast[ByteAddress](b):
pthread_mutex_lock(a) pthread_mutex_lock(a)
@ -1717,6 +1676,7 @@ level. This then means that the routine may acquire locks of up to this level.
This is essential so that procs can be called within a `locks` section: This is essential so that procs can be called within a `locks` section:
.. code-block:: nim .. code-block:: nim
proc p() {.locks: 3.} = discard proc p() {.locks: 3.} = discard
var a: TLock[4] var a: TLock[4]
@ -1739,6 +1699,7 @@ cannot be inferred statically, leading to compiler warnings. By using
having unknown lock level as well: having unknown lock level as well:
.. code-block:: nim .. code-block:: nim
type SomeBase* = ref object of RootObj type SomeBase* = ref object of RootObj
type SomeDerived* = ref object of SomeBase type SomeDerived* = ref object of SomeBase
memberProc*: proc () memberProc*: proc ()
@ -1761,6 +1722,7 @@ e.g. when rewriting term to same term plus extra content.
e.g. with given example `echo("ab")` will be rewritten just once: e.g. with given example `echo("ab")` will be rewritten just once:
.. code-block:: nim .. code-block:: nim
template pwnEcho{echo(x)}(x: untyped) = template pwnEcho{echo(x)}(x: untyped) =
{.noRewrite.}: echo("pwned!") {.noRewrite.}: echo("pwned!")