I made an attempt to do this with macros
    
    
    import strformat, macros, macrocache
    
    const varCache = CacheTable"vars"
    
    macro getVar(varId: static string): untyped =
      let v = varCache[varId][1]
      result = quote do:
        when not declared(`v`):
          {.error: "Don't mark variable as immutable in nested scope".}
        else:
          `v`
    
    macro mutable*(id, typ, val: untyped): untyped =
      let varId = &"mutable::{id}::{id.lineInfo}"
      let varName = ident varId
      varCache[varId] = newStmtList(id, varName)
      let typ =
        if typ.kind == nnkNilLit: newEmptyNode()
        else: typ
      let templDefn = newProc(id, params = [ident "untyped"], procType = 
nnkTemplateDef,
                              body = newStmtList(newCall(bindSym "getVar", 
newLit varId)))
      result = newStmtList(nnkVarSection.newTree(newIdentDefs(varName, typ, 
val)), templDefn)
    
    macro immutable*(id: typed): untyped =
      if id.symKind == nskLet:
        error(&"identifier is already immutable", result)
      let varId = $id
      let varName = $varCache[varId][0]
      let newName = ident (&":{varName} is now immutable:")
      result = newLetStmt(newName, id)
      varCache[varId][1] = newName
    
    type
      Baz = object
        x: int
        y: string
    
    proc test() =
      let foo {.mutable.} = @["A"]
      foo.add "B"
      foo.add "C"
      immutable foo
      #foo.add "D" # Compiler error
      echo foo
      
      let bar {.mutable.} = [1, 2, 3]
      bar[0] = 10
      bar[1] = 20
      immutable bar
      #bar[2] = 30 # Compiler error
      echo bar
      
      let baz {.mutable.} = Baz(x: 1, y: "Hello")
      baz.x = 5
      echo baz
      immutable baz
      #baz.y = "World" # Compiler error
    
    test()
    
    
    
    Run

Reply via email to