doc improvements (#18843)
* cleaned up destructors documentation [backport] * Spec updates [backport:1.0]
This commit is contained in:
parent
3f3e0fa303
commit
179fad934d
2 changed files with 33 additions and 138 deletions
|
|
@ -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.
|
|
||||||
|
|
|
||||||
118
doc/manual.rst
118
doc/manual.rst
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue