discard """
action: "run"
output: '''
B[system.int]
A[system.string]
A[array[0..0, int]]
A[seq[int]]
100
a
b
c
a
b
c
1
2
'''
"""
import conceptsv2_helper

block: # issue  #24451
  type
    A = object
      x: int
    B[T] = object
      b: T
    AConcept = concept
      proc implementation(s: var Self, p1: B[int])

  proc implementation(r: var A, p1: B[int])=
    echo typeof(p1)

  proc accept(r: var AConcept)=
    r.implementation(B[int]())

  var a = A()
  a.accept()

block: # typeclass
  type
    A[T] = object
      x: int
    AConcept = concept
      proc implementation(s: Self)

  proc implementation(r: A) =
    echo typeof(r)

  proc accept(r: AConcept) =
    r.implementation()

  var a = A[string]()
  a.accept()

block:
  type
    SomethingLike[T] = concept
      proc len(s: Self): int
      proc `[]`(s: Self; index: int): T

    A[T] = object
      x: T

  proc initA(x: SomethingLike): auto =
    A[type x](x: x)

  var a: array[1, int]
  var s: seq[int]
  echo typeof(initA(a))
  echo typeof(initA(s))

block:
  proc iGetShadowed(s: int)=
    discard
  proc spring(x: ShadowConcept)=
    discard
  let a = DummyFitsObj()
  spring(a)

block:
  type
    Buffer = concept
      proc put(s: Self)
    ArrayBuffer[T: static int] = object
  proc put(x: ArrayBuffer)=discard
  proc p(a: Buffer)=discard
  var buffer = ArrayBuffer[5]()
  p(buffer)

block: # composite typeclass matching
  type
    A[T] = object
    Buffer = concept
      proc put(s: Self, i: A)
    BufferImpl = object
    WritableImpl = object

  proc launch(a: var Buffer)=discard
  proc put(x: BufferImpl, i: A)=discard

  var a = BufferImpl()
  launch(a)

block: # simple recursion
  type
    Buffer = concept
      proc put(s: var Self, i: auto)
      proc second(s: Self)
    Writable = concept
      proc put(w: var Buffer, s: Self)
    BufferImpl[T: static int] = object
    WritableImpl = object

  proc launch(a: var Buffer, b: Writable)= discard
  proc put[T](x: var BufferImpl, i: T)= discard
  proc second(x: BufferImpl)= discard
  proc put(x: var Buffer, y: WritableImpl)= discard

  var a = BufferImpl[5]()
  launch(a, WritableImpl())

block: # more complex recursion
  type
    Buffer = concept
      proc put(s: var Self, i: auto)
      proc second(s: Self)
    Writable = concept
      proc put(w: var Buffer, s: Self)
    BufferImpl[T: static int] = object
    WritableImpl = object

  proc launch(a: var Buffer, b: Writable)= discard
  proc put[T](x: var Buffer, i: T)= discard
  proc put(x: var BufferImpl, i: object)= discard
  proc second(x: BufferImpl)= discard
  proc put(x: var Buffer, y: WritableImpl)= discard

  var a = BufferImpl[5]()
  launch(a, WritableImpl())

block: # co-dependent concepts
  type
    Writable = concept
      proc w(b: var Buffer; s: Self): int
    Buffer = concept
      proc w(s: var Self; data: Writable): int
    SizedWritable = concept
      proc size(x: Self): int
      proc w(b: var Buffer, x: Self): int
    BufferImpl = object
    
  proc w(x: var BufferImpl, d: int): int = return 100
  proc size(d: int): int = sizeof(int)

  proc p(b: var Buffer, data: SizedWritable): int =
    b.w(data)

  var b = BufferImpl()
  echo p(b, 5)

block: # indirect concept matching
  type
    Sizeable = concept
      proc size(s: Self): int
    Buffer = concept
      proc w(s: Self, data: Sizeable)
    Serializable = concept
      proc something(s: Self)
      proc w(b: Buffer, s: Self)
    BufferImpl = object
    ArrayImpl = object

  proc something(s: ArrayImpl)= discard
  proc size(s: ArrayImpl): int= discard

  proc w(x: BufferImpl, d: Sizeable)= discard

  proc spring(s: Buffer, data: Serializable)=discard

  spring(BufferImpl(), ArrayImpl())

block: # instantiate even when generic params are the same
  type
    ArrayLike[T] = concept
      proc len(x: Self): int
      proc `[]`(b: Self, i: int): T
  proc p[T](x: ArrayLike[T])=
    for k in x:
      echo k
  # For this test to work the second call's instantiation has to be incompatible with the first on the back end
  p(['a','b','c'])
  p("abc")

block: # reject improper generic variables in candidates
  type
    ArrayLike[T] = concept
      proc len(x: Self): int
      proc g(b: Self, i: int): T
    FreakString = concept
      proc len(x: Self): int
      proc characterSize(s: Self): int 
    A = object

  proc g[T, H](s: T, i: H): H = default(T)
  proc len(s: A): int = discard
  proc characterSize(s: A): int = discard
  
  proc p(symbol: ArrayLike[char]): int = assert false
  proc p(symbol: FreakString): int=discard
  
  discard p(A())

block: # typerel disambiguation by concept subset
  type
    ArrayLike[T] = concept
      proc len(x: Self): int
      proc characterSize(s: Self): int
    FreakString = concept
      proc len(x: Self): int
      proc characterSize(s: Self): int
      proc tieBreaker(s: Self, j: int): float
    A = object

  proc len(s: A): int = discard
  proc characterSize(s: A): int = discard
  proc tieBreaker(s: A, h: int):float = 0.0
  
  proc p(symbol: ArrayLike[char]): int = assert false
  proc p(symbol: FreakString): int=discard
  
  discard p(A())

block:  # tie break via sumGeneric
  type
    C1 = concept
      proc p1(x: Self, b: int)
      proc p2(x: Self, b: float)
      proc p3(x: Self, b: string)
    C2 = concept
      proc b1(x: Self, b: int)
      proc b2(x: Self, b: float)
    A = object
  
  proc p1(x: A, b: int)=discard
  proc p2(x: A, b: float)=discard
  proc p3(x: A, b: string)=discard
  
  proc b1(x: A, b: int)=discard
  proc b2(x: A, b: float)=discard
  
  proc p(symbol: C1): int = discard
  proc p(symbol: C2): int = assert false
  
  discard p(A())

block: # not type
  type
    C1 = concept
      proc p(s: Self, a: int)
    C1Impl = object
    
  proc p(x: C1Impl, a: not float)= discard
  proc spring(x: C1)= discard

  spring(C1Impl())

block: # not type parameterized
  type
    C1[T: not int] = concept
      proc p(s: Self, a: T)
    C1Impl = object
    
  proc p(x: C1Impl, a: float)= discard
  proc spring(x: C1)= discard

  spring(C1Impl())

block: # typedesc
  type
    C1 = concept
      proc p(s: Self, a: typedesc[SomeInteger])
    C1Impl = object
    
  proc p(x: C1Impl, a: typedesc)= discard
  proc spring(x: C1)= discard

  spring(C1Impl())


block: # or
  type
    C1 = concept
      proc p(s: Self, a: int | float)
    C1Impl = object
    
  proc p(x: C1Impl, a: int | float | string)= discard
  proc spring(x: C1)= discard

  spring(C1Impl())

block: # or mixed generic param
  type
    C1 = concept
      proc p(s: Self, a: int | float)
    C1Impl = object
    
  proc p[T: string | float](x: C1Impl, a: int | T) = discard
  proc spring(x: C1)= discard

  spring(C1Impl())

block: # or parameterized
  type
    C1[T: int | float | string] = concept
      proc p(s: Self, a: T)
    C1Impl = object
    
  proc p(x: C1Impl, a: int | float)= discard
  proc spring(x: C1)= discard

  spring(C1Impl())

block: # unconstrained param
  type
    A = object
    C1[T] = concept
      proc p(s: Self, a: T)
    C1Impl = object
    
  proc p(x: C1Impl, a: A)= discard
  proc spring(x: C1)= discard

  spring(C1Impl())

block: # unconstrained param sanity check
  type
    A = object
    C1[T: auto] = concept
      proc p(s: Self, a: T)
    C1Impl = object
    
  proc p(x: C1Impl, a: A)= discard
  proc spring(x: C1)= discard

  spring(C1Impl())

block: # exact nested concept binding
  type
    Sizeable = concept
      proc size(s: Self): int
    Buffer = concept
      proc w(s: Self, data: Sizeable)
    Serializable = concept
      proc w(b: Buffer, s: Self)
    ArrayLike = concept
      proc len(s: Self): int
    ArrayImpl = object

  proc len(s: ArrayImpl): int = discard
  proc w(x: Buffer, d: ArrayLike)=discard

  proc spring(data: Serializable)=discard
  spring(ArrayImpl())

block:
  type
    StaticallySized = concept
      proc staticSize(x: typedesc[Self]): int
      proc dynamicSize(x: Self): int
    DynamicallySized = concept
      proc dynamicSize(x: Self): int

  proc dynamicSize(a: SomeInteger): int = 5
  proc read[T: DynamicallySized](a: var T): int = 1
  proc read[T: SomeInteger](a: var T): int = 2

  var a: uint16
  assert read(a) == 2

block:
  type
    A[X, Y] = object
      x: X
      y: Y
    C1 = concept
      proc p(z: var Self)
    C2 = concept
      proc g(x: var Self, y: int)
    C3 = C1 and C2
    C4 = concept
      proc h(x: Self): C3

  proc p[X, Y](z: var A[int, float]) = discard
  proc g[X, Y](z: var A[X, Y], y: int) = discard
  proc h[X, Y](z: A[X, Y]): A[X, Y] = discard

  proc spring(x: C4) = discard
  var d = A[int, float]()
  d.spring()

block:
  type
    A[X, Y] = object
      x: X
      y: Y
    B = object
    C1 = concept
      proc p(z: var Self, d: A[int, float])

  proc p[X: int; Y: float](x: var B, y: A[X, Y]) = discard
  proc spring(x: var C1) = discard
  var d = B()
  d.spring()

block:
  type
    A = object
    C1 = concept
      proc p(s: Self; x: auto)
    C2[T: int] = concept
      proc p(s: Self; x: T)
    Impl = object

  proc p(n: Impl; i: int) = discard

  proc spring(x: C1): int = 1
  proc spring(x: C2): int = 2

  assert spring(Impl()) == 2

block:
  type
    C1[T] = concept
      proc p(s: var Self; x: T)
    FreakString = concept
      proc p(w: var C1; s: Self)
      proc a(x: Self)
    DynArray[CT, T] = object

  proc p[CT; T; W; ](w: C1[T]; o: DynArray[CT, T]): int = discard
  proc spring(s: auto) = discard
  proc spring(s: FreakString) = discard

  spring("hi")

block:
  type
    RawWriter = concept
      proc write(s: Self; data: pointer; length: int)
    ArrayBuffer[N: static int] = object
    SeqBuffer = object
    CompatBuffer = ArrayBuffer | SeqBuffer

  proc write[T:CompatBuffer](a: var T; data: pointer; length: int) =
    discard

  proc spring(r:RawWriter, i: byte)=discard

  var s = ArrayBuffer[1500]()
  spring(s, 8.uint8)

block:
  type
    Future[T] = object
    SyncType = concept
      proc p(s: Self)
    AsyncType = concept
      proc p(s: Self) : Future[void]
    SyncImpl = object
    AsyncImpl = object
    Container[T] = object

  proc p(x: SyncImpl) = discard
  proc p(x: AsyncImpl): Future[void] = discard

  proc p(x: Container[SyncType]) = discard
  proc p(x: Container[AsyncImpl]): Future[void] = discard

  assert SyncImpl is SyncType
  assert SyncImpl isnot AsyncType
  assert AsyncImpl isnot SyncType
  assert AsyncImpl is AsyncType
  assert Container[SyncImpl] is SyncType
  assert Container[SyncImpl] isnot AsyncType
  assert Container[AsyncImpl] isnot SyncType
  assert Container[AsyncImpl] is AsyncType

block:
  type
    C1 = concept
      proc p(x: typedesc[Self]): int
    E1 = enum
      One, Two
  proc p[E: enum](x: typedesc[set[E]]): int = sizeof(set[E])

  proc spring(x: C1) = discard

  spring({One,Two})

block: # bare `range`
  type
    MyRange = 0..64
    MyConcept = concept
      proc a(x: typedesc[Self])

  proc a(x: typedesc[range]) = discard
  proc spring(x: typedesc[MyConcept]) = discard
  spring(MyRange)

block:
  type
    A = object
    TestConcept =
      concept
          proc x(x: Self)

  proc x(x: not object) =
    discard

  assert A isnot TestConcept

# this code fails inside a block for some reason
type Indexable[T] = concept
  proc `[]`(t: Self, i: int): T
  proc len(t: Self): int

iterator items[T](t: Indexable[T]): T =
  for i in 0 ..< t.len:
    yield t[i]

type Enumerable[T] = concept
  iterator items(t: Self): T

proc echoAll[T](t: Enumerable[T]) =
  for item in t:
    echo item

type DummyIndexable[T] = distinct seq[T]

proc `[]`[T](t: DummyIndexable[T], i: int): T =
  seq[T](t)[i]

proc len[T](t: DummyIndexable[T]): int =
  seq[T](t).len


let dummyIndexable = DummyIndexable(@[1, 2])
echoAll(dummyIndexable)

block:
  type
    C = concept
      proc a(x: Self, i: int)
    AObj[T] = object
      x: T
    ARef[T] = ref AObj[T]

  proc a[T: int](x: ARef[T], i: int) =
    discard

  assert (ref AObj[int]) is C

block:
  type
    C = concept
      proc a(x: Self, i: int)
    AObj[T; B] = object
      x: T
    ARef[T; B] = ref AObj[T,B]

  proc a[T: int, C: float](x: ARef[T, C], i: int) =
    discard

  assert (ref AObj[int, int]) isnot C
  assert (ref AObj[int, float]) is C

block:
  type
    C = concept
      proc a(x: Self, i: int)
    AObj[T] = object
    ARef[T] = ref AObj[T]

  proc a(x: ARef, i: int) =
    discard

  assert (ref AObj[int]) is C

block:
  type 
    C = concept
      proc x(a:Self, x: int)
    StreamObj = object of RootObj
    Stream = ref StreamObj
    MemMapFileStreamObj = object of Stream
    MemMapFileStream = ref MemMapFileStreamObj

  proc x(a: Stream, x: int) = discard
  proc spring(x: C) = discard

  let test = MemMapFileStream()
  spring(test)
