doc improvements (#18843)

* cleaned up destructors documentation [backport]

* Spec updates [backport:1.0]
This commit is contained in:
Andreas Rumpf
2021-09-13 11:05:09 +02:00
committed by GitHub
parent 3f3e0fa303
commit 179fad934d
2 changed files with 33 additions and 138 deletions

View File

@@ -222,7 +222,14 @@ The prototype of this hook for a type `T` needs to be:
`env` is used by ORC to keep track of its internal state, it should be passed around
to calls of the built-in `=trace` operation.
Usually there will only be a need for a custom `=trace` when a custom `=destroy` that deallocates manually allocated resources is also used, and then only when there is a chance of cyclic references from items within the manually allocated resources when it is desired that `--gc:orc` be able to break and collect these cyclic referenced resources. Currently however, there is a mutual use problem in that whichever of `=destroy`/`=trace` is used first will automatically create a version of the other which will then conflict with the creation of the second of the pair. The work around for this problem is to forward declare the second of the "hooks" to prevent the automatic creation.
Usually there will only be a need for a custom `=trace` when a custom `=destroy` that deallocates
manually allocated resources is also used, and then only when there is a chance of cyclic
references from items within the manually allocated resources when it is desired that `--gc:orc`
be able to break and collect these cyclic referenced resources. Currently however, there is a
mutual use problem in that whichever of `=destroy`/`=trace` is used first will automatically
create a version of the other which will then conflict with the creation of the second of the
pair. The work around for this problem is to forward declare the second of the "hooks" to
prevent the automatic creation.
The general pattern in using `=destroy` with `=trace` looks like:
@@ -696,43 +703,22 @@ should eventually be replaced by a better solution.
Copy on write
=============
String literals are implemented as [copy-on-write](https://en.wikipedia.org/wiki/Copy-on-write).
String literals are implemented as "copy on write".
When assigning a string literal to a variable, a copy of the literal won't be created.
Instead the variable simply points to the literal.
The literal is shared between different variables which are pointing to it.
The copy operation is deferred until the first write.
.. code-block:: nim
var x = "abc" # no copy
var y = x # no copy
The string literal "abc" is stored in static memory and not allocated on the heap.
The variable `x` points to the literal and the variable `y` points to the literal too.
There is no copy during assigning operations.
For example:
.. code-block:: nim
var x = "abc" # no copy
var y = x # no copy
y[0] = 'h' # copy
The program above shows when the copy operations happen.
When mutating the variable `y`, the Nim compiler creates a fresh copy of `x`,
the variable `y` won't point to the string literal anymore.
Instead it points to the copy of `x` of which the memory can be mutated
and the variable `y` becomes a mutable string.
.. Note:: The abstraction fails for `addr x` because whether the address is going to be used for mutations is unknown.
Let's look at a silly example demonstrating this behaviour:
.. code-block:: nim
var x = "abc"
var y = x
moveMem(addr y[0], addr x[0], 3)
The program fails because we need to prepare a fresh copy for the variable `y`.
`prepareMutation` should be called before the address operation.
The abstraction fails for `addr x` because whether the address is going to be used for mutations is unknown.
`prepareMutation` needs to be called before the "address of" operation. For example:
.. code-block:: nim
var x = "abc"
@@ -741,18 +727,3 @@ The program fails because we need to prepare a fresh copy for the variable `y`.
prepareMutation(y)
moveMem(addr y[0], addr x[0], 3)
assert y == "abc"
Now `prepareMutation` solves the problem.
It manually creates a fresh copy and makes the variable `y` mutable.
.. code-block:: nim
var x = "abc"
var y = x
prepareMutation(y)
moveMem(addr y[0], addr x[0], 3)
moveMem(addr y[0], addr x[0], 3)
moveMem(addr y[0], addr x[0], 3)
assert y == "abc"
No matter how many times `moveMem` is called, the program compiles and runs.

View File

@@ -2400,121 +2400,38 @@ describe the type checking done by the compiler.
Type equality
-------------
Nim uses structural type equivalence for most types. Only for objects,
enumerations and distinct types name equivalence is used. The following
algorithm, *in pseudo-code*, determines type equality:
.. code-block:: nim
proc typeEqualsAux(a, b: PType,
s: var HashSet[(PType, PType)]): bool =
if (a,b) in s: return true
incl(s, (a,b))
if a.kind == b.kind:
case a.kind
of int, intXX, float, floatXX, char, string, cstring, pointer,
bool, nil, void:
# leaf type: kinds identical; nothing more to check
result = true
of ref, ptr, var, set, seq, openarray:
result = typeEqualsAux(a.baseType, b.baseType, s)
of range:
result = typeEqualsAux(a.baseType, b.baseType, s) and
(a.rangeA == b.rangeA) and (a.rangeB == b.rangeB)
of array:
result = typeEqualsAux(a.baseType, b.baseType, s) and
typeEqualsAux(a.indexType, b.indexType, s)
of tuple:
if a.tupleLen == b.tupleLen:
for i in 0..a.tupleLen-1:
if not typeEqualsAux(a[i], b[i], s): return false
result = true
of object, enum, distinct:
result = a == b
of proc:
result = typeEqualsAux(a.parameterTuple, b.parameterTuple, s) and
typeEqualsAux(a.resultType, b.resultType, s) and
a.callingConvention == b.callingConvention
proc typeEquals(a, b: PType): bool =
var s: HashSet[(PType, PType)] = {}
result = typeEqualsAux(a, b, s)
Since types are graphs which can have cycles, the above algorithm needs an
auxiliary set `s` to detect this case.
Type equality modulo type distinction
-------------------------------------
The following algorithm (in pseudo-code) determines whether two types
are equal with no respect to `distinct` types. For brevity the cycle check
with an auxiliary set `s` is omitted:
.. code-block:: nim
proc typeEqualsOrDistinct(a, b: PType): bool =
if a.kind == b.kind:
case a.kind
of int, intXX, float, floatXX, char, string, cstring, pointer,
bool, nil, void:
# leaf type: kinds identical; nothing more to check
result = true
of ref, ptr, var, set, seq, openarray:
result = typeEqualsOrDistinct(a.baseType, b.baseType)
of range:
result = typeEqualsOrDistinct(a.baseType, b.baseType) and
(a.rangeA == b.rangeA) and (a.rangeB == b.rangeB)
of array:
result = typeEqualsOrDistinct(a.baseType, b.baseType) and
typeEqualsOrDistinct(a.indexType, b.indexType)
of tuple:
if a.tupleLen == b.tupleLen:
for i in 0..a.tupleLen-1:
if not typeEqualsOrDistinct(a[i], b[i]): return false
result = true
of distinct:
result = typeEqualsOrDistinct(a.baseType, b.baseType)
of object, enum:
result = a == b
of proc:
result = typeEqualsOrDistinct(a.parameterTuple, b.parameterTuple) and
typeEqualsOrDistinct(a.resultType, b.resultType) and
a.callingConvention == b.callingConvention
elif a.kind == distinct:
result = typeEqualsOrDistinct(a.baseType, b)
elif b.kind == distinct:
result = typeEqualsOrDistinct(a, b.baseType)
enumerations and distinct types and for generic types name equivalence is used.
Subtype relation
----------------
If object `a` inherits from `b`, `a` is a subtype of `b`. This subtype
relation is extended to the types `var`, `ref`, `ptr`:
.. code-block:: nim
proc isSubtype(a, b: PType): bool =
if a.kind == b.kind:
case a.kind
of object:
var aa = a.baseType
while aa != nil and aa != b: aa = aa.baseType
result = aa == b
of var, ref, ptr:
result = isSubtype(a.baseType, b.baseType)
If object `a` inherits from `b`, `a` is a subtype of `b`.
.. XXX nil is a special value!
This subtype relation is extended to the types `var`, `ref`, `ptr`.
If `A` is a subtype of `B` and `A` and `B` are `object` types then:
- `var A` is a subtype of `var B`
- `ref A` is a subtype of `ref B`
- `ptr A` is a subtype of `ptr B`.
**Note**: In later versions of the language the subtype relation might
be changed to *require* the pointer indirection in order to prevent
"object slicing".
Convertible relation
--------------------
A type `a` is **implicitly** convertible to type `b` iff the following
algorithm returns true:
.. code-block:: nim
proc isImplicitlyConvertible(a, b: PType): bool =
if isSubtype(a, b) or isCovariant(a, b):
if isSubtype(a, b):
return true
if isIntLiteral(a):
return b in {int8, int16, int32, int64, int, uint, uint8, uint16,
@@ -2540,7 +2457,12 @@ algorithm returns true:
result = b == pointer
of string:
result = b == cstring
of proc:
result = typeEquals(a, b) or compatibleParametersAndEffects(a, b)
We used the predicate `typeEquals(a, b)` for the "type equality" property
and the predicate `isSubtype(a, b)` for the "subtype relation".
`compatibleParametersAndEffects(a, b)` is currently not specified.
Implicit conversions are also performed for Nim's `range` type
constructor.
@@ -2563,7 +2485,9 @@ algorithm returns true:
proc isExplicitlyConvertible(a, b: PType): bool =
result = false
if isImplicitlyConvertible(a, b): return true
if typeEqualsOrDistinct(a, b): return true
if typeEquals(a, b): return true
if a == distinct and typeEquals(a.baseType, b): return true
if b == distinct and typeEquals(b.baseType, a): return true
if isIntegralType(a) and isIntegralType(b): return true
if isSubtype(a, b) or isSubtype(b, a): return true