Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bind generic param/or to constraint for conversion match #24250

Open
wants to merge 1 commit into
base: devel
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)