nim-lang / Nim

Nim is a statically typed compiled systems programming language. It combines successful concepts from mature languages like Python, Ada and Modula. Its design focuses on efficiency, expressiveness, and elegance (in that order of priority).
https://nim-lang.org
Other
16.64k stars 1.47k forks source link

New Style Concept Matches When It Should Not #21250

Open c-blake opened 1 year ago

c-blake commented 1 year ago

Description

Define a module lpt.nim (idea being a generic hash table with user types):

type
  LPs*[K,V] = concept
    proc key(c: Self, i: int): K        # Cell ops, i-th various
    proc val(c: Self, i: int): V        # Cell ops, i-th various
  Void* = distinct int
  Voids* = Void|distinct char

proc incl*[K,V: Voids](s: var LPs[K,V], key: K) =
  when V is not Voids: echo "whoa" # runs yet call matched!?
  echo "did incl"

iterator pairs*[K,V:not Voids](s: LPs[K,V]): (K, V) =
  var k: K; var v: V
  for i in 1..3: yield (k, v)

Now a client module, kv.nim that accesses the not Voids branch of the type constraint compiles when it should fail:

import lpt

var str = newStringOfCap(81920) # Unique data stacked bk2bk
const bOff = 26 # <64 MiB UNIQUE word data
const bLen =  6 # <64B long; Give 1 To Below?
const bCnt = 32 # <2 GiCount; Could be RT w/less convenience
type
  Count {.packed.} = object     # Dense-ish hash Count type
    off {.bitsize: bOff.}: uint32
    len {.bitsize: bLen.}: uint8
    cnt {.bitsize: bCnt.}: uint32
  Counts = object
    dat: seq[Count]
    len: int

proc key(c: Count): string = str[c.off ..< c.off + c.len]
proc `==`(a: string|Count, b: Count):bool = true
proc key(c: Counts, i: int): string = c.dat[i].key
proc val(c: Counts, i: int): uint32 = c.dat[i].cnt
proc incFailed(h: var Counts, ms: string): bool = false

proc main() =
  var h: Counts
  var nTot = 0
  block IO:
    for line in stdin.lines:
      inc nTot                        # Always bump `nTot`
      if h.incFailed(line): break IO
  echo h.len," unique ",nTot," total ",str.len," B"
  for (k, c) in h.pairs: echo c," ",k
  h.incl "heyho"                      # Should fail;DOESN'T
main()

However, a key-only variant ko.nim works & fails correctly (EDIT: and differs from kv.nim only in the lack of bCnt & cnt and the return type of val):

import lpt

var str = newStringOfCap(81920) # Unique data stacked bk2bk
const bOff = 26 # <64 MiB UNIQUE word data
const bLen =  6 # <64B long; Give 1 To Below?
type
  Count {.packed.} = object     # Dense-ish hash Count type
    off {.bitsize: bOff.}: uint32
    len {.bitsize: bLen.}: uint8
  Counts = object
    dat: seq[Count]
    len: int

proc key(c: Count): string = str[c.off ..< c.off + c.len]
proc `==`(a: string|Count, b: Count):bool = true
proc key(c: Counts, i: int): string = c.dat[i].key
proc val(c: Counts, i: int): Void {.used.} = discard
proc incFailed(h: var Counts, ms: string): bool = false

proc main() =
  var h: Counts
  var nTot = 0
  block IO:
    for line in stdin.lines:
      inc nTot                        # Always bump `nTot`
      if h.incFailed(line): break IO
  echo h.len," unique ",nTot," total ",str.len," B"
# for (k, c) in h.pairs: echo c," ",k # Should & DOES fail
  h.incl "heyho"
main()

Nim Version

Nim Compiler Version 1.9.1 [Linux: amd64] Compiled at 2023-01-10 Copyright (c) 2006-2023 by Andreas Rumpf

active boot switches: -d:release -d:danger -d:nimUseLinenoise -d:nimHasLibFFI

commit hash: b68b28fd2448b15e989b9e8b07e33eee4f9b8822

However, this has been failing for at least the last few weeks of nim-devel and also fails on nim-1.6.10

Current Output

{ EDIT: When given an empty file as stdin, } the incorrect kv.nim output is

0 unique 0 total 0 B
0
0
0
whoa
did incl

while the correct ko.nim output is

0 unique 0 total 0 B
did incl

Expected Output

ko.nim output is as expected. kv.nim is not expected to compile.. Additionally, it is not expected to both sigmatch the incl and emit "whoa\n" from the when.

Possible Solution

No response

Additional Information

No response

metagn commented 1 week ago

Maybe simplified:

type
  Foo[T] = concept
    proc foo(c: Self): T
  Obj* = object

proc foo(x: Obj): int = discard

proc bar[T: not int](s: Foo[T]) = discard

bar(Obj())

Generic constraint is ignored