Skip to content

Commit

Permalink
bind generic param/or to constraint for conversion match
Browse files Browse the repository at this point in the history
  • Loading branch information
metagn committed Oct 7, 2024
1 parent ea9811a commit 9954dff
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 12 deletions.
20 changes: 20 additions & 0 deletions compiler/layeredtable.nim
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,23 @@ proc put(typeMap: var LayeredIdTable, key: ItemId, value: PType) {.inline.} =
template put*(typeMap: var LayeredIdTable, key, value: PType) =
## binds `key` to `value` only in current layer
put(typeMap, key.itemId, value)

proc putRecursive(typeMap: ref LayeredIdTableObj, key: ItemId, value: PType) =
var tm = typeMap
while tm != nil:
tm.topLayer[key] = value
tm = tm.nextLayer

template putRecursive*(typeMap: ref LayeredIdTableObj, key, value: PType) =
## binds `key` to `value` in all previous layers
putRecursive(typeMap, key.itemId, value)

when not useRef:
proc putRecursive(typeMap: var LayeredIdTableObj, key: ItemId, value: PType) {.inline.} =
put(typeMap, key, value)
if typeMap.nextLayer != nil:
putRecursive(typeMap.nextLayer, key, value)

template putRecursive*(typeMap: var LayeredIdTableObj, key, value: PType) =
## binds `key` to `value` in all previous layers
putRecursive(typeMap, key.itemId, value)
77 changes: 66 additions & 11 deletions compiler/sigmatch.nim
Original file line number Diff line number Diff line change
Expand Up @@ -1280,12 +1280,17 @@ proc typeRel(c: var TCandidate, f, aOrig: PType,
if a.isResolvedUserTypeClass:
return typeRel(c, f, a.skipModifier, flags)

template bindingRet(res) =
template bindingRet(res, bound) =
if doBind:
let bound = aOrig.skipTypes({tyRange}).skipIntLit(c.c.idgen)
put(c, f, bound)
return res

template defaultBinding(): PType =
aOrig#[.skipTypes({tyRange})]#.skipIntLit(c.c.idgen)

template bindingRet(res) =
bindingRet(res, defaultBinding())

template considerPreviousT(body: untyped) =
var prev = lookup(c.bindings, f)
if prev == nil: body
Expand Down Expand Up @@ -1831,18 +1836,42 @@ proc typeRel(c: var TCandidate, f, aOrig: PType,
result = isNone
let oldInheritancePenalty = c.inheritancePenalty
var minInheritance = maxInheritancePenalty
var bestMatch: PType = nil
# the best match in the branches so far, preferring earlier branches
# this is to resolve ambiguity in cases like matching an int literal
# against `int8 | int16`, picking the earlier `int8`
# this keeps compatibility with legacy behavior
for branch in f.kids:
c.inheritancePenalty = -1
let x = typeRel(c, branch, aOrig, flags)
if x >= result:
if c.inheritancePenalty > -1:
minInheritance = min(minInheritance, c.inheritancePenalty)
if c.inheritancePenalty < minInheritance:
minInheritance = c.inheritancePenalty
if x > result:
# has to be strictly better so we prefer earlier matches
# also true for inheritance penalty in this case
bestMatch = branch
elif x > result:
# has to be strictly better so we prefer earlier matches
bestMatch = branch
result = x
if result >= isIntConv:
if minInheritance < maxInheritancePenalty:
c.inheritancePenalty = oldInheritancePenalty + minInheritance
var bound: PType = nil
if (result in {isConvertible, isIntConv, isSubrange, isFromIntLit} or
(result == isSubtype and a.isEmptyContainer)) and bestMatch != nil:
# match needs a conversion, bind to the constraint so the
# conversion can be generated
# supertypes act like typeclasses, only consider as concrete for
# empty collections
bound = bestMatch
else:
# default, bind to matched type
bound = defaultBinding()
if result > isGeneric: result = isGeneric
bindingRet result
bindingRet result, bound
else:
result = isNone
of tyNot:
Expand Down Expand Up @@ -1924,6 +1953,7 @@ proc typeRel(c: var TCandidate, f, aOrig: PType,
let doBindGP = doBind or trBindGenericParam in flags
var x = lookup(c.bindings, f)
if x == nil:
var bound: PType = nil
if c.callee.kind == tyGenericBody and not c.typedescMatched:
# XXX: The fact that generic types currently use tyGenericParam for
# their parameters is really a misnomer. tyGenericParam means "match
Expand Down Expand Up @@ -1957,11 +1987,35 @@ proc typeRel(c: var TCandidate, f, aOrig: PType,
else:
# check if 'T' has a constraint as in 'proc p[T: Constraint](x: T)'
if f.len > 0 and f[0].kind != tyNone:
result = typeRel(c, f[0], a, flags + {trDontBind, trBindGenericParam})
# constraints need a new type binding context layer,
# so that matches to typeclasses in the constraint don't
# bind to that typeclass for the entire candidate
c.bindings = newTypeMapLayer(c.bindings)
# generic params in the constraint are an exception,
# so that we can infer things like `T: U`
result = typeRel(c, f[0], a, flags + {trBindGenericParam})
if f[0].skipTypes({tyAlias}).kind == tyOr:
# `or` types have special binding rules, they bind to
# one of their branches if the match needs a conversion
# we reuse this binding for the generic parameter
bound = lookup(c.bindings, f[0])
if bound == nil: bound = a
elif result in {isConvertible, isIntConv, isSubrange, isFromIntLit} or
(result == isSubtype and a.isEmptyContainer):
# match needs a conversion, bind to the constraint so the
# conversion can be generated
# supertypes act like typeclasses, only consider as concrete for
# empty collections
bound = f[0]
else:
# default, bind to matched type
bound = a
setToPreviousLayer(c.bindings)
if doBindGP and result notin {isNone, isGeneric}:
let concrete = concreteType(c, a, f)
let concrete = concreteType(c, bound, f)
if concrete == nil: return isNone
put(c, f, concrete)
# generic params have to be bound up to the root binding context
putRecursive(c.bindings, f, concrete)
if result in {isEqual, isSubtype}:
result = isGeneric
elif a.kind == tyTypeDesc:
Expand All @@ -1973,7 +2027,7 @@ proc typeRel(c: var TCandidate, f, aOrig: PType,
result = isGeneric

if result == isGeneric:
var concrete = a
if bound == nil: bound = a
if tfWildcard in a.flags:
a.sym.transitionGenericParamToType()
a.flags.excl tfWildcard
Expand All @@ -1983,11 +2037,12 @@ proc typeRel(c: var TCandidate, f, aOrig: PType,
# reason about and maintain. Refactoring typeRel to not be responsible for setting, or
# at least validating, bindings can have multiple benefits. This is debatable. I'm not 100% sure.
# A design that allows a proper complexity analysis of types like `tyOr` would be ideal.
concrete = concreteType(c, a, f)
if concrete == nil:
bound = concreteType(c, bound, f)
if bound == nil:
return isNone
if doBindGP:
put(c, f, concrete)
# generic params have to be bound up to the root binding context
putRecursive(c.bindings, f, bound)
elif result > isGeneric:
result = isGeneric
elif a.kind == tyEmpty:
Expand Down
2 changes: 1 addition & 1 deletion lib/pure/os.nim
Original file line number Diff line number Diff line change
Expand Up @@ -978,7 +978,7 @@ proc setLastModificationTime*(file: string, t: times.Time) {.noWeirdTarget.} =
## an error.
when defined(posix):
let unixt = posix.Time(t.toUnix)
let micro = convert(Nanoseconds, Microseconds, t.nanosecond)
let micro = convert(Nanoseconds, Microseconds, t.nanosecond).int32
var timevals = [Timeval(tv_sec: unixt, tv_usec: micro),
Timeval(tv_sec: unixt, tv_usec: micro)] # [last access, last modification]
if utimes(file, timevals.addr) != 0: raiseOSError(osLastError(), file)
Expand Down
64 changes: 64 additions & 0 deletions tests/overload/torconv.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
block:
proc foo(x: int16 | int32): string = $typeof(x)
proc bar[T: int16 | int32](x: T): string = $typeof(x)

doAssert foo(123) == "int16"
doAssert bar(123) == "int16"

block: # issue #4858
type
SomeType = object
field1: uint

proc namedProc(an: var SomeType, b: SomeUnsignedInt) = discard

proc `+=`(an: var SomeType, b: SomeUnsignedInt) =
namedProc(an, b) # <---- error here

var t = SomeType()
namedProc(t, 0)
t += 0

block: # issue #10027
type Uint24 = range[0'u32 .. 0xFFFFFF'u32]

proc a(v: SomeInteger|Uint24): string = $type(v)

doAssert a(42) == "int"
doAssert a(42.Uint24) == $Uint24

block: # issue #12552
let x = 1'i8
proc foo(n : int): string = $typeof(n)
proc bar[T : int](n : T): string = $ typeof(n)
doAssert foo(x) == "int"
doAssert bar(x) == "int"

block: # issue #15721
proc fn(a = 4, b: seq[string] or tuple[] = ()) =
discard # eg: when b is tuple[]: ...
fn(1)
fn(1, @[""])
var a: seq[string] = @[]
fn(1, a)
fn(1, seq[string](@[]))
fn(1, @[]) # BUG: error: conflicting types for 'fn__d58I39cH9a6bcpi3QDPJ5dBA'

block: # issue #15721, set
proc fn(a = 4, b: set[uint8] or tuple[] = ()) =
discard # eg: when b is tuple[]: ...
fn(1)
fn(1, {1'u8})
var a: set[uint8] = {}
fn(1, a)
fn(1, set[uint8]({}))
fn(1, {}) # BUG: internal error: invalid kind for lastOrd(tyEmpty)

block: # issue #21331
let a : int8 | uint8 = 3
doAssert sizeof(a)==sizeof(int8) # this fails

block:
let x: range[0..5] = 1
proc foo[T: SomeInteger](x: T): string = $typeof(x)
discard foo(x)

0 comments on commit 9954dff

Please sign in to comment.