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

better dependent-expression support for range types #850

Merged
Show file tree
Hide file tree
Changes from 4 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
8 changes: 8 additions & 0 deletions compiler/ast/ast_query.nim
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,14 @@ proc isMetaType*(t: PType): bool =
proc isUnresolvedStatic*(t: PType): bool =
return t.kind == tyStatic and t.n == nil

proc isUnresolvedSym*(s: PSym): bool =
result = s.kind == skGenericParam
if not result and s.typ != nil:
result = tfInferrableStatic in s.typ.flags or
(s.kind == skParam and s.typ.isMetaType) or
(s.kind == skType and
s.typ.flags * {tfGenericTypeParam, tfImplicitTypeParam} != {})

template fileIdx*(c: PSym): FileIndex =
assert c.kind == skModule, "this should be used only on module symbols"
c.position.FileIndex
Expand Down
10 changes: 1 addition & 9 deletions compiler/sem/semexprs.nim
Original file line number Diff line number Diff line change
Expand Up @@ -869,14 +869,6 @@ proc fixAbstractType(c: PContext, n: PNode): PNode =
proc isAssignable(c: PContext, n: PNode; isUnsafeAddr=false): TAssignableResult =
result = parampatterns.isAssignable(c.p.owner, n, isUnsafeAddr)

proc isUnresolvedSym(s: PSym): bool =
result = s.kind == skGenericParam
if not result and s.typ != nil:
result = tfInferrableStatic in s.typ.flags or
(s.kind == skParam and s.typ.isMetaType) or
(s.kind == skType and
s.typ.flags * {tfGenericTypeParam, tfImplicitTypeParam} != {})

proc hasUnresolvedArgs(c: PContext, n: PNode): bool =
# Checks whether an expression depends on generic parameters that
# don't have bound values yet. E.g. this could happen in situations
Expand Down Expand Up @@ -1582,7 +1574,7 @@ proc readTypeParameter(c: PContext, typ: PType,
paramName: PIdent, info: TLineInfo): PNode =
# Note: This function will return emptyNode when attempting to read
# a static type parameter that is not yet resolved (e.g. this may
# happen in proc signatures such as `proc(x: T): array[T.sizeParam, U]`
# happen in types such as ``type Typ[T] = arary[T.sizeParam, int]``)
if typ.kind in {tyUserTypeClass, tyUserTypeClassInst}:
for statement in typ.n:
case statement.kind
Expand Down
2 changes: 1 addition & 1 deletion compiler/sem/semstmts.nim
Original file line number Diff line number Diff line change
Expand Up @@ -2275,7 +2275,7 @@ proc semInferredLambda(c: PContext, pt: TIdTable, n: PNode): PNode {.nosinks.} =
#incl(s.flags, sfFromGeneric)
#s.owner = original

n = replaceTypesInBody(c, pt, n, original)
n = instantiateTypesInBody(c, pt, n, original)
result = n
s.ast = result
n[namePos].sym = s
Expand Down
127 changes: 86 additions & 41 deletions compiler/sem/semtypes.nim
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,38 @@ proc semDistinct(c: PContext, n: PNode, prev: PType): PType =

from std/sequtils import mapIt

proc fixupTypeVars(c: PContext, n: var PNode): bool =
## Takes AST that is produced by the generic pre-pass as input and:
## 1. makes sure all unresolved type variables are referenced via their
## *symbol*. Necesary because the pre-pass leaves those as raw identifiers
## in some cases.
## 2. computes whether unresolved type variables are referenced
##
## If there are no type variables, 'false' is returned and the AST remains
## unchanged.
case n.kind
of nkSym:
if isUnresolvedSym(n.sym):
n.typ = n.sym.typ
result = true
of nkIdent, nkAccQuoted:
let (ident, err) = considerQuotedIdent(c, n)
assert err == nil, "should have been handled in semgnrc"

var amb = false
let sym = searchInScopes(c, ident, amb)
if sym != nil and isUnresolvedSym(sym):
# replace with the symbol of the type variable
n = newSymNode(sym, n.info)
result = true
of nkWithoutSons - {nkSym, nkIdent}:
discard "nothing to do"
of nkWithSons - {nkAccQuoted}:
for i in 0..<n.len:
let r = fixupTypeVars(c, n[i])
# propagate upwards whether a type var was fixed:
result = result or r

proc semRangeAux(c: PContext, n: PNode, prev: PType): PType =
assert isRange(n)
checkSonsLen(n, 3, c.config)
Expand All @@ -290,16 +322,35 @@ proc semRangeAux(c: PContext, n: PNode, prev: PType): PType =
if (n[1].kind == nkEmpty) or (n[2].kind == nkEmpty):
localReport(c.config, n, reportSem rsemRangeIsEmpty)

# run the pre-pass for generic statements first: we don't know
# whether or not the expression references type variables
var range = [semGenericStmt(c, n[1]), semGenericStmt(c, n[2])]

for it in range.mitems:
if fixupTypeVars(c, it):
if it.kind != nkSym:
# the expression needs a type, and since its a dependent one, a
# ``tyFromExpr`` is used
it.typ = makeTypeFromExpr(c, it.copyTree)

# make sure the expression later takes the correct path through
# ``replaceTypeVarsN``:
it = makeStaticExpr(c, it)
result.flags.incl tfUnresolved
else:
# the expression must be constant
let e = semExprWithType(c, it)
it = evalConstExpr(c, e)

result.n.add it

let
range = [semExprWithType(c, n[1]), semExprWithType(c, n[2])]
rangeT = range.mapIt(it.typ.skipTypes({tyStatic}).skipIntLit(c.idgen))
hasUnknownTypes = c.inGenericContext > 0 and
tyFromExpr in {rangeT[0].kind, rangeT[1].kind}
hasUnknownTypes = tyFromExpr in {rangeT[0].kind, rangeT[1].kind}

if not hasUnknownTypes:
if not sameType(rangeT[0].skipTypes({tyRange}), rangeT[1].skipTypes({tyRange})):
# XXX: should this cascade and what about the follow-on statements like
# the for loop, etc below?
# XXX: errors from the previous analysis need to be taken into account
let r = typeMismatch(c.config, n.info, rangeT[0], rangeT[1], n)
if r.kind == nkError:
localReport(c.config, r)
Expand All @@ -312,13 +363,6 @@ proc semRangeAux(c: PContext, n: PNode, prev: PType): PType =
elif enumHasHoles(rangeT[0]):
localReport(c.config, n.info, reportTyp(rsemExpectedUnholyEnum, rangeT[0]))

for i in 0..1:
if hasUnresolvedArgs(c, range[i]):
result.n.add makeStaticExpr(c, range[i])
result.flags.incl tfUnresolved
else:
result.n.add semConstExpr(c, range[i])

if (result.n[0].kind in {nkFloatLit..nkFloat64Lit} and result.n[0].floatVal.isNaN) or
(result.n[1].kind in {nkFloatLit..nkFloat64Lit} and result.n[1].floatVal.isNaN):
localReport(c.config, n, reportSem rsemRangeDoesNotSupportNan)
Expand Down Expand Up @@ -360,10 +404,35 @@ proc semArrayIndex(c: PContext, n: PNode): PType =
if isRange(n):
result = semRangeAux(c, n, nil)
else:
let e = semExprWithType(c, n)
if e.typ.kind == tyFromExpr:
result = makeRangeWithStaticExpr(c, e.typ.n)
elif e.kind in {nkIntLit..nkUInt64Lit}:
# the expression may depend on type variables, meaning that we have to
# treat it as generic first
# XXX: only run the generics pre-pass if the possibiltiy of unresolved type
# variables being used actually exists. At the time of writing,
# ``inGenericContext`` cannot be used for this, as it is not increased
# for implicitly and explicitly generic routines
var e = semGenericStmt(c, n)
if fixupTypeVars(c, e):
# an expression that references type variables
if e.kind == nkSym:
if e.typ.kind == tyStatic:
result = makeRangeWithStaticExpr(c, e)
result.flags.incl tfUnresolved
else:
# a type variable is used directly as the index type (e.g.,
# ``array[T, ...]``). Don't create a range, but use the type
# directly
result = e.typ
else:
# the type of the expression depends on the expression itself; signal
# this via a ``tyFromExpr``
e.typ = makeTypeFromExpr(c, copyTree(e))
result = makeRangeWithStaticExpr(c, e)
result.flags.incl tfUnresolved
return

# an expression that doesn't reference type variables
e = semExprWithType(c, e)
if e.kind in {nkIntLit..nkUInt64Lit}:
if e.intVal < 0:
localReport(c.config, n.info,
SemReport(
Expand All @@ -372,30 +441,6 @@ proc semArrayIndex(c: PContext, n: PNode): PType =
got: toInt128 e.intVal))

result = makeRangeType(c, 0, e.intVal-1, n.info, e.typ)
elif e.kind == nkSym and e.typ.kind == tyStatic:
if e.sym.ast != nil:
return semArrayIndex(c, e.sym.ast)
if not isOrdinalType(e.typ.lastSon):
let info = if n.safeLen > 1: n[1].info else: n.info
localReport(c.config, info, reportTyp(
rsemExpectedOrdinal, e.typ.lastSon))

result = makeRangeWithStaticExpr(c, e)
if c.inGenericContext > 0:
result.flags.incl tfUnresolved

elif e.kind in (nkCallKinds + {nkBracketExpr}) and hasUnresolvedArgs(c, e):
if not isOrdinalType(e.typ.skipTypes({tyStatic, tyAlias, tyGenericInst, tySink})):
localReport(c.config, n[1].info, reportTyp(
rsemExpectedOrdinal, e.typ))
# This is an int returning call, depending on an
# yet unknown generic param (see tgenericshardcases).
# We are going to construct a range type that will be
# properly filled-out in semtypinst (see how tyStaticExpr
# is handled there).
result = makeRangeWithStaticExpr(c, e)
elif e.kind == nkIdent:
result = e.typ.skipTypes({tyTypeDesc})
else:
let x = semConstExpr(c, e)
if x.kind in {nkIntLit..nkUInt64Lit}:
Expand All @@ -413,7 +458,7 @@ proc semArray(c: PContext, n: PNode, prev: PType): PType =
var indxB = indx
if indxB.kind in {tyGenericInst, tyAlias, tySink}: indxB = lastSon(indxB)
if indxB.kind notin {tyGenericParam, tyStatic, tyFromExpr}:
if indxB.skipTypes({tyRange}).kind in {tyUInt, tyUInt64}:
if indxB.skipTypes({tyRange}).kind in {tyUInt, tyUInt64, tyFromExpr}:
discard
elif not isOrdinalType(indxB):
localReport(c.config, n[1].info, reportTyp(
Expand Down
Loading
Loading