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
commit 179fad934d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
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 `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. 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: 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 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. 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. Instead the variable simply points to the literal.
The literal is shared between different variables which are pointing to it. The literal is shared between different variables which are pointing to it.
The copy operation is deferred until the first write. The copy operation is deferred until the first write.
.. code-block:: nim For example:
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.
.. code-block:: nim .. code-block:: nim
var x = "abc" # no copy var x = "abc" # no copy
var y = x # no copy var y = x # no copy
y[0] = 'h' # 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. 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:
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.
.. code-block:: nim .. code-block:: nim
var x = "abc" var x = "abc"
@ -741,18 +727,3 @@ The program fails because we need to prepare a fresh copy for the variable `y`.
prepareMutation(y) prepareMutation(y)
moveMem(addr y[0], addr x[0], 3) moveMem(addr y[0], addr x[0], 3)
assert y == "abc" 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 Type equality
------------- -------------
Nim uses structural type equivalence for most types. Only for objects, Nim uses structural type equivalence for most types. Only for objects,
enumerations and distinct types name equivalence is used. The following enumerations and distinct types and for generic types name equivalence is used.
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)
Subtype relation 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 If object `a` inherits from `b`, `a` is a subtype of `b`.
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)
.. 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 Convertible relation
-------------------- --------------------
A type `a` is **implicitly** convertible to type `b` iff the following A type `a` is **implicitly** convertible to type `b` iff the following
algorithm returns true: algorithm returns true:
.. code-block:: nim .. code-block:: nim
proc isImplicitlyConvertible(a, b: PType): bool = proc isImplicitlyConvertible(a, b: PType): bool =
if isSubtype(a, b) or isCovariant(a, b): if isSubtype(a, b):
return true return true
if isIntLiteral(a): if isIntLiteral(a):
return b in {int8, int16, int32, int64, int, uint, uint8, uint16, return b in {int8, int16, int32, int64, int, uint, uint8, uint16,
@ -2540,7 +2457,12 @@ algorithm returns true:
result = b == pointer result = b == pointer
of string: of string:
result = b == cstring 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 Implicit conversions are also performed for Nim's `range` type
constructor. constructor.
@ -2563,7 +2485,9 @@ algorithm returns true:
proc isExplicitlyConvertible(a, b: PType): bool = proc isExplicitlyConvertible(a, b: PType): bool =
result = false result = false
if isImplicitlyConvertible(a, b): return true 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 isIntegralType(a) and isIntegralType(b): return true
if isSubtype(a, b) or isSubtype(b, a): return true if isSubtype(a, b) or isSubtype(b, a): return true