> The accessor are only visible for the internal library proc but not exported 
> outside of the library.

In this case I cannot rely on hiding the fields via re-exporting, because the 
library is by design supposed to be extended on user side. Think of an UI 
component library, where users should be able to define their own components.

@lscrd Good points about the runtime checks, but yes compile time security 
would be preferred.

I have now found two solutions that seem to satisfy the requirements:

1\. Macro solution:
    
    
    # on lib side
    template privateInitializer(element: typed, idString: string): untyped =
      element.id = idString
      element
    
    macro newElement*(T: typedesc, id: string, args: varargs[untyped]): untyped 
=
      # convert varargs into an object constructor call of T
      let constructor = newTree(nnkObjConstr)
      constructor.add(T.getTypeInst[1])
      for arg in args:
        expectKind(arg, nnkExprEqExpr)
        constructor.add(newColonExpr(arg[0], arg[1]))
      # apply post construction initialization of parent fields
      result = newCall(bindSym"privateInitializer", constructor, id)
    
    # on user side
    newElement(ElementA, id="A", a=1)
    
    
    Run

2\. Template solution with an explicit check that only object constructors are 
passed in:
    
    
    # on lib side
    macro verifyObjectConstructor(x: typed): untyped =
      if x.kind != nnkObjConstr:
        error($x.repr[0..^1] & " is not an object constructor")
    
    template newElement*(idString: string, constructor: typed): untyped =
      verifyObjectConstructor(constructor)
      let element = constructor
      element.id = idString
      element
    
    # on user side
    newElement("A", ElementA(a: 1))
    
    # abusing the template as an arbitrary setter is now a compile time error:
    let el = ElementA(a: 1)
    newElement("A", el)
    
    
    Run

@Araq: What is your opinion on this? I'm wondering if it would make sense to 
differentiate visibility for construction vs field access. As far as I can see 
a lot of visibility problems would be solved if there was an intermediate 
between fully-hidden and fully-exposed which is "exposed in object 
construction". Would it for instance make sense to have
    
    
    type
      Element* = ref object of RootObj
        id {.initializable.}: string
    
    
    Run

so that public access to `id` is prevented in general, but `id` can be passed 
into the constructor including subtype constructors?

Reply via email to