It's less than ideal, but thanks to `converter` and distinct a subset of 
refinement types are possible with present Nim.
    
    
    import std/[macros, genasts]
    
    type RefinementError* = object of CatchableError
    
    macro refineType(name: untyped, base: typedesc, expressions: 
varargs[untyped]): untyped =
      if expressions.len == 0:
        error("Cannot refine nothing, need to provide atleast one requirement", 
expressions)
      var expr = expressions[0]
      for arg in expressions[1..^1]:
        expr = infix(expr, "and", arg)
      
      result = genast(name, base, expr):
        type name* {.borrow: `.`.} = distinct base
        converter toBase*(val: name): base = base(val)
        #converter toBase*(val: var name): var base = base(val)
        converter `to name`(it {.inject.}: base): name =
          if not expr:
            raise (ref RefinementError)(msg: "Failed to refine to type.")
          name(it)
        converter `to name`(it {.inject.}: var base): var name =
          if not expr:
            raise (ref RefinementError)(msg: "Failed to refine to type.")
          name(it)
    
    
    
    refineType DivBy10, int, (it mod 10 == 0)
    
    var a: DivBy10 = 20
    doAssertRaises RefinementError: a = a + 3
    a = a + 10
    
    type MyType = object
      case kind: uint8
      of 0, 1, 2:
        a, b: int
      of 3, 4:
        c, d: float
      else:
        e, f: string
    
    refineType Atom, MyType, it.kind in {0u8, 1, 2}
    
    proc doThing(atom: Atom): string = $atom.a & $atom.b
    
    assert MyType(kind: 0, a: 300, b: 400).doThing == "300400"
    doAssertRaises RefinementError: discard MyType(kind: 3, c: 300, d: 
400).doThing()
    
    
    Run

I was too lazy to do it this way but using type section macros one could also 
write it in the more clear syntax as the following.
    
    
    type Atom {.refinement.} = concept it
      it.kind in {0u8, 1, 2}
    
    
    Run

Reply via email to