No description
Find a file
2019-05-10 21:56:39 -04:00
finals Turn into a Nimble package. 2018-09-20 21:55:34 -05:00
tests Fixed a buggo 2019-05-07 20:37:48 -04:00
.gitignore Remove binaries; gitignore 2018-09-20 22:41:52 -04:00
finals.nim Remove echo :^) 2019-05-10 21:56:39 -04:00
finals.nimble Remove echo :^) 2019-05-10 21:56:39 -04:00
readme.md add finalsOn and finalsOff to readme 2019-05-07 16:23:01 -04:00

finals

(Mostly) Transparent single-set attributes for Nim types

Example

finals:
  type Point = object
    x*: int
    y* {.final.}: int

# In another file...

var p: Point
p.x = 3  # fine
p.y = 4  # fine
p.x = 0  # fine
p.y = 1  # Error! `y` can only be set once!

Usage

Like in the example. Generalizes to variant types as well. In a finals: block, multiple typedefs may be present, as well as non-typdefs, which will be ignored.

For debugging

finalsd may be used instead of finals to only have an effect with -d:debug, and otherwise be a 0-cost noop.

If Nim is run with -d:finalsOn, both finals and finalsd blocks will run. If Nim is run with -d:finalsOff, neither finals nor finalsd blocks will run. If Nim is run with both -d:finalsOn and -d:finalsOff, the entire world will explode.

Gotchas

Name conflicts

The finals macro works by generating, for an attribute a: A on type T, a getter proc a(o: T): A and setter proc `a=`(o: T; v: A). If you define your own custom setters and getters, there will be a name conflict.

Module scoping

Unfortunately, the finals macro only has an effect outside of the module containing the typedef. This is because getters and setters are ignored within the same module. It's due to a Nim feature and is unavoidable.

# a.nim
finals:
  type X = object
    x* {.final.}: int

X.x will only be protected for .x= calls from outside a.nim. This means that the following will not result in an error:

# a.nim (continued)
var o* = X()
o.x = 3

# b.nim
import a
o.x = 4

In order to circumvent this issue, an user may manually finalize an attribute X by calling ffinalizeX(o) (the extra "f" stands for "finals" is added to avoid name conflicts). The previous example would be fixed like so:

# a.nim (continued)
var o* = X()
o.x = 3
o.finalizeX();

# b.nim
import a
o.x = 4  # error!

Contructors and Object Hierarchies

In order for the finals macro to work, attributes marked with {.final.} are explicitely NOT exported. Due to this, there is an issue with constructors and object hierarchies:

finals:
  type Parent* = ref object
    p* {.final.}: int
  type Child* = ref object
    c* {.final.}: int

let c = Child(c: 2, p: 3)  # error! `p` is not accessible

Function Attributes

Because finals generates accessors for attriubutes, obj.finalAttr() will call the acceessor rather than invoking the attribute.

finals:
  type T = object
    f* {.final.}: proc()

proc initT():
  return T(f: proc() = echo "test")

# in another file...
let t = initT()
t.f()  # calls the `f` accessor instead of the proc
t.f()()  # need two ()'s to work

Known bugs

Intolerant of other pragmas

If an attribute is marked with any pragmas besides {.final.}, those pragmas will be removed.