Remove Covariance section from the experimental manual (#18688)
* Remove `Covariance` section * Add blank lines after `.. code-block::` * Fix CI?
This commit is contained in:
parent
c07d8da7b9
commit
0ddd9519c0
1 changed files with 63 additions and 101 deletions
|
|
@ -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:
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
proc nothing(x, y: void): void =
|
||||
echo "ha"
|
||||
|
||||
|
|
@ -78,6 +79,7 @@ the procedure does not return a value:
|
|||
The `void` type is particularly useful for generic code:
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
proc callProc[T](p: proc (x: T), x: T) =
|
||||
when T is void:
|
||||
p()
|
||||
|
|
@ -93,6 +95,7 @@ The `void` type is particularly useful for generic code:
|
|||
However, a `void` type cannot be inferred in generic code:
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
callProc(emptyProc)
|
||||
# Error: type mismatch: got (proc ())
|
||||
# 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`.
|
||||
|
||||
|
||||
|
||||
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
|
||||
=======================
|
||||
|
||||
|
|
@ -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".}`:
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
{.experimental: "implicitDeref".}
|
||||
|
||||
proc depth(x: NodeObj): int = ...
|
||||
|
|
@ -219,6 +123,7 @@ This feature has to be enabled via `{.experimental: "implicitDeref".}`:
|
|||
echo n.depth
|
||||
# no need to write n[].depth either
|
||||
|
||||
|
||||
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:
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
{.experimental: "codeReordering".}
|
||||
|
||||
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-block:: nim
|
||||
|
||||
{.experimental: "codeReordering".}
|
||||
|
||||
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:*
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
{.experimental: "codeReordering".}
|
||||
|
||||
proc a() =
|
||||
|
|
@ -331,6 +239,7 @@ has different names. This does not need an `experimental` switch, but is an
|
|||
unstable feature.
|
||||
|
||||
.. code-block:: Nim
|
||||
|
||||
proc foo(x: int) =
|
||||
echo "Using x: ", x
|
||||
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:
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
sort(cities) do (x,y: string) -> int:
|
||||
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:
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
macro performWithUndo(task, undo: untyped) = ...
|
||||
|
||||
performWithUndo do:
|
||||
|
|
@ -380,7 +291,6 @@ pass multiple blocks to a macro:
|
|||
# code to undo it
|
||||
|
||||
|
||||
|
||||
Special Operators
|
||||
=================
|
||||
|
||||
|
|
@ -404,6 +314,7 @@ the expression, where the unknown field or proc name is passed to
|
|||
an `untyped` parameter:
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
a.b # becomes `.`(a, b)
|
||||
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:
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
template `.`(js: PJsonNode, field: untyped): JSON = js[astToStr(field)]
|
||||
|
||||
var js = parseJson("{ x: 1, y: 2}")
|
||||
|
|
@ -435,6 +347,7 @@ operator `.=`
|
|||
This operator will be matched against assignments to missing fields.
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
a.b = c # becomes `.=`(a, b, c)
|
||||
|
||||
Call operator
|
||||
|
|
@ -445,6 +358,7 @@ for existing routines. The experimental `callOperator` switch must be enabled
|
|||
to use this operator.
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
{.experimental: "callOperator".}
|
||||
|
||||
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:
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
{.experimental: "notnil".}
|
||||
|
||||
type
|
||||
|
|
@ -502,6 +417,7 @@ here.
|
|||
|
||||
.. include:: manual_experimental_strictnotnil.rst
|
||||
|
||||
|
||||
Concepts
|
||||
========
|
||||
|
||||
|
|
@ -511,6 +427,7 @@ arbitrary set of requirements that the matched type must satisfy.
|
|||
Concepts are written in the following form:
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
type
|
||||
Comparable = concept x, y
|
||||
(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:
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
type
|
||||
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:
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
type
|
||||
OutputStream = concept var s
|
||||
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.
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
type
|
||||
# Let's imagine a user-defined casting framework with operators
|
||||
# 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.
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
type
|
||||
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:
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
### matrixalgo.nim
|
||||
|
||||
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:
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
type
|
||||
MatrixReducer[M, N: static int; T] = concept x
|
||||
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:
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
type
|
||||
Enumerable[T] = concept 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:
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
type
|
||||
MyConcept = concept x
|
||||
type T1 = auto
|
||||
|
|
@ -776,6 +701,7 @@ accessible through the dot operator in procs where the concept was successfully
|
|||
matched to a concrete type:
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
type
|
||||
DateTime = concept t1, t2, type T
|
||||
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:
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
type
|
||||
Graph = concept g, type G of EquallyComparable, Copyable
|
||||
type
|
||||
|
|
@ -853,6 +780,7 @@ object inheritance syntax involving the `of` keyword:
|
|||
the concept body:
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
type
|
||||
Stringable = concept x
|
||||
$x is string
|
||||
|
|
@ -904,6 +832,7 @@ object inheritance syntax involving the `of` keyword:
|
|||
types to the corresponding VTable type.
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
type
|
||||
IntEnumerable = vtref Enumerable[int]
|
||||
|
||||
|
|
@ -970,6 +899,7 @@ language may weaken this restriction.)
|
|||
The signature has to be:
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
proc `=deepCopy`(x: T): T
|
||||
|
||||
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:
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
template optMul{`*`(a, 2)}(a: int): int = a+a
|
||||
|
||||
let x = 3
|
||||
|
|
@ -1059,6 +990,7 @@ Unfortunately optimizations are hard to get right and even the tiny example
|
|||
is **wrong**:
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
template optMul{`*`(a, 2)}(a: int): int = a+a
|
||||
|
||||
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:
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
template optMul{`*`(a, 2)}(a: int{noSideEffect}): int = a+a
|
||||
|
||||
proc f(): int =
|
||||
|
|
@ -1088,11 +1021,13 @@ cannot really do that however as the following code only swaps arguments
|
|||
blindly:
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
template mulIsCommutative{`*`(a, b)}(a, b: int): int = b*a
|
||||
|
||||
What optimizers really need to do is a *canonicalization*:
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
template canonMul{`*`(a, b)}(a: int{lit}, b: int): int = b*a
|
||||
|
||||
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:
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
template ex{a = b + c}(a: int{noalias}, b, c: int) =
|
||||
# this transformation is only valid if 'b' and 'c' do not alias 'a':
|
||||
a = b
|
||||
|
|
@ -1160,6 +1096,7 @@ the ordinary AST predicates:
|
|||
Another example:
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
proc somefunc(s: string) = assert s == "variable"
|
||||
proc somefunc(s: string{nkStrLit}) = assert s == "literal"
|
||||
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:
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
template t{0|1}(): untyped = 3
|
||||
let a = 1
|
||||
# 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:
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
template t{0|1}(): untyped = 3
|
||||
# outputs 1:
|
||||
echo 1
|
||||
|
|
@ -1215,6 +1154,7 @@ A pattern expression can be bound to a pattern parameter via the `expr{param}`
|
|||
notation:
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
template t{(0|1|2){x}}(x: untyped): untyped = x+1
|
||||
let a = 1
|
||||
# outputs 2:
|
||||
|
|
@ -1227,6 +1167,7 @@ The `~` operator
|
|||
The `~` operator is the **not** operator in patterns:
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
template t{x = (~x){y} and (~x){z}}(x, y, z: bool) =
|
||||
x = y
|
||||
if x: x = z
|
||||
|
|
@ -1246,6 +1187,7 @@ The `*` operator can *flatten* a nested binary expression like `a & b & c`
|
|||
to `&(a, b, c)`:
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
var
|
||||
calls = 0
|
||||
|
||||
|
|
@ -1270,6 +1212,7 @@ which is flattened into a call expression; thus the invocation of `optConc`
|
|||
produces:
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
`&&`("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:
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
import std/macros
|
||||
|
||||
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:
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
template optWrite{
|
||||
write(f, x)
|
||||
((write|writeLine){w})(f, y)
|
||||
|
|
@ -1339,6 +1284,7 @@ The following example shows how some simple partial evaluation can be
|
|||
implemented with term rewriting:
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
proc p(x, y: int; cond: bool): int =
|
||||
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:
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
import std/pegs
|
||||
|
||||
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:
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
proc optLit(a: string{lit|`const`}) =
|
||||
echo "string literal"
|
||||
proc optLit(a: string) =
|
||||
|
|
@ -1426,6 +1374,7 @@ Spawn statement
|
|||
`spawn`:idx: can be used to pass a task to the thread pool:
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
import std/threadpool
|
||||
|
||||
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:
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
import std/threadpool, ...
|
||||
|
||||
# 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:
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
var glock: TLock
|
||||
var gdata {.guard: glock.}: int
|
||||
|
||||
|
|
@ -1559,6 +1510,7 @@ The compiler then ensures that every access of `gdata` is within a `locks`
|
|||
section:
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
proc invalid =
|
||||
# invalid: unguarded access:
|
||||
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:
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
template lock(a: TLock; body: untyped) =
|
||||
pthread_mutex_lock(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:
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
var dummyLock {.compileTime.}: 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:
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
type
|
||||
ProtectedCounter = object
|
||||
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:
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
proc incCounters(counters: var openArray[ProtectedCounter]) =
|
||||
for i in 0..counters.high:
|
||||
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:
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
{.locks: [a[i].L].}:
|
||||
inc i
|
||||
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:
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
var a, b: TLock[2]
|
||||
var x: TLock[1]
|
||||
# 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:
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
template multilock(a, b: ptr TLock; body: untyped) =
|
||||
if cast[ByteAddress](a) < cast[ByteAddress](b):
|
||||
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:
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
proc p() {.locks: 3.} = discard
|
||||
|
||||
var a: TLock[4]
|
||||
|
|
@ -1739,6 +1699,7 @@ cannot be inferred statically, leading to compiler warnings. By using
|
|||
having unknown lock level as well:
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
type SomeBase* = ref object of RootObj
|
||||
type SomeDerived* = ref object of SomeBase
|
||||
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:
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
template pwnEcho{echo(x)}(x: untyped) =
|
||||
{.noRewrite.}: echo("pwned!")
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue