Skip to content

Commit

Permalink
make expressions opt in to symchoices (#22716)
Browse files Browse the repository at this point in the history
refs #22605

Sym choice nodes are now only allowed to pass through semchecking if
contexts ask for them to (with `efAllowSymChoice`). Otherwise they are
resolved or treated as ambiguous. The contexts that can receive
symchoices in this PR are:

* Call operands and addresses and emulations of such, which will subject
them to overload resolution which will resolve them or fail.
* Type conversion operands only for routine symchoices for type
disambiguation syntax (like `(proc (x: int): int)(foo)`), which will
resolve them or fail.
* Proc parameter default values both at the declaration and during
generic instantiation, which undergo type narrowing and so will resolve
them or fail.

This means unless these contexts mess up sym choice nodes should never
leave the semchecking stage. This serves as a blueprint for future
improvements to intermediate symbol resolution.

Some tangential changes are also in this PR:

1. The `AmbiguousEnum` hint is removed, it was always disabled by
default and since #22606 it only started getting emitted after the
symchoice was soundly resolved.
2. Proc setter syntax (`a.b = c` becoming `` `b=`(a, c) ``) used to
fully type check the RHS before passing the transformed call node to
proc overloading. Now it just passes the original node directly so proc
overloading can deal with its typechecking.

(cherry picked from commit 5f9038a)
  • Loading branch information
metagn authored and narimiran committed Aug 14, 2024
1 parent 7b834b9 commit b479462
Show file tree
Hide file tree
Showing 11 changed files with 71 additions and 60 deletions.
1 change: 0 additions & 1 deletion compiler/condsyms.nim
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,6 @@ proc initDefines*(symbols: StringTableRef) =
defineSymbol("nimHasTemplateRedefinitionPragma")
defineSymbol("nimHasCstringCase")
defineSymbol("nimHasCallsitePragma")
defineSymbol("nimHasAmbiguousEnumHint")

defineSymbol("nimHasWarnCastSizes") # deadcode
defineSymbol("nimHasOutParams")
Expand Down
2 changes: 0 additions & 2 deletions compiler/lineinfos.nim
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,6 @@ type
hintPattern = "Pattern", hintExecuting = "Exec", hintLinking = "Link", hintDependency = "Dependency",
hintSource = "Source", hintPerformance = "Performance", hintStackTrace = "StackTrace",
hintGCStats = "GCStats", hintGlobalVar = "GlobalVar", hintExpandMacro = "ExpandMacro",
hintAmbiguousEnum = "AmbiguousEnum",
hintUser = "User", hintUserRaw = "UserRaw", hintExtendedContext = "ExtendedContext",
hintMsgOrigin = "MsgOrigin", # since 1.3.5
hintDeclaredLoc = "DeclaredLoc", # since 1.5.1
Expand Down Expand Up @@ -228,7 +227,6 @@ const
hintGCStats: "$1",
hintGlobalVar: "global variable declared here",
hintExpandMacro: "expanded macro: $1",
hintAmbiguousEnum: "$1",
hintUser: "$1",
hintUserRaw: "$1",
hintExtendedContext: "$1",
Expand Down
1 change: 1 addition & 0 deletions compiler/semdata.nim
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ type
efNoDiagnostics,
efTypeAllowed # typeAllowed will be called after
efWantNoDefaults
efAllowSymChoice # symchoice node should not be resolved

TExprFlags* = set[TExprFlag]

Expand Down
99 changes: 47 additions & 52 deletions compiler/semexprs.nim
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ template rejectEmptyNode(n: PNode) =
proc semOperand(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
rejectEmptyNode(n)
# same as 'semExprWithType' but doesn't check for proc vars
result = semExpr(c, n, flags + {efOperand})
result = semExpr(c, n, flags + {efOperand, efAllowSymChoice})
if result.typ != nil:
if result.typ.kind in {tyVar, tyLent}: result = newDeref(result)
elif {efWantStmt, efAllowStmt} * flags != {}:
Expand All @@ -79,42 +79,10 @@ proc semExprCheck(c: PContext, n: PNode, flags: TExprFlags, expectedType: PType
# do not produce another redundant error message:
result = errorNode(c, n)

proc ambiguousSymChoice(c: PContext, orig, n: PNode): PNode =
let first = n[0].sym
var foundSym: PSym = nil
if first.kind == skEnumField and
not isAmbiguous(c, first.name, {skEnumField}, foundSym) and
foundSym == first:
# choose the first resolved enum field, i.e. the latest in scope
# to mirror behavior before overloadable enums
if hintAmbiguousEnum in c.config.notes:
var err = "ambiguous enum field '" & first.name.s &
"' assumed to be of type " & typeToString(first.typ) &
" -- use one of the following:\n"
for child in n:
let candidate = child.sym
err.add " " & candidate.owner.name.s & "." & candidate.name.s & "\n"
message(c.config, orig.info, hintAmbiguousEnum, err)
result = n[0]
else:
var err = "ambiguous identifier '" & first.name.s &
"' -- use one of the following:\n"
for child in n:
let candidate = child.sym
err.add " " & candidate.owner.name.s & "." & candidate.name.s
err.add ": " & typeToString(candidate.typ) & "\n"
localError(c.config, orig.info, err)
n.typ = errorType(c)
result = n

proc semExprWithType(c: PContext, n: PNode, flags: TExprFlags = {}, expectedType: PType = nil): PNode =
result = semExprCheck(c, n, flags-{efTypeAllowed}, expectedType)
if result.typ == nil and efInTypeof in flags:
result.typ = c.voidType
elif (result.typ == nil or result.typ.kind == tyNone) and
efTypeAllowed in flags and
result.kind == nkClosedSymChoice and result.len > 0:
result = ambiguousSymChoice(c, n, result)
elif result.typ == nil or result.typ == c.enforceVoidContext:
localError(c.config, n.info, errExprXHasNoType %
renderTree(result, {renderNoComments}))
Expand Down Expand Up @@ -147,6 +115,39 @@ proc semExprNoDeref(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
proc semSymGenericInstantiation(c: PContext, n: PNode, s: PSym): PNode =
result = symChoice(c, n, s, scClosed)

proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode

proc isSymChoice(n: PNode): bool {.inline.} =
result = n.kind in nkSymChoices

proc semSymChoice(c: PContext, n: PNode, flags: TExprFlags = {}, expectedType: PType = nil): PNode =
result = n
if expectedType != nil:
result = fitNode(c, expectedType, result, n.info)
if isSymChoice(result) and efAllowSymChoice notin flags:
# some contexts might want sym choices preserved for later disambiguation
# in general though they are ambiguous
let first = n[0].sym
var foundSym: PSym = nil
if first.kind == skEnumField and
not isAmbiguous(c, first.name, {skEnumField}, foundSym) and
foundSym == first:
# choose the first resolved enum field, i.e. the latest in scope
# to mirror behavior before overloadable enums
result = n[0]
else:
var err = "ambiguous identifier '" & first.name.s &
"' -- use one of the following:\n"
for child in n:
let candidate = child.sym
err.add " " & candidate.owner.name.s & "." & candidate.name.s
err.add ": " & typeToString(candidate.typ) & "\n"
localError(c.config, n.info, err)
n.typ = errorType(c)
result = n
if result.kind == nkSym:
result = semSym(c, result, result.sym, flags)

proc inlineConst(c: PContext, n: PNode, s: PSym): PNode {.inline.} =
result = copyTree(s.astdef)
if result.isNil:
Expand Down Expand Up @@ -286,9 +287,6 @@ proc isCastable(c: PContext; dst, src: PType, info: TLineInfo): bool =
if result and src.kind == tyNil:
return dst.size <= conf.target.ptrSize

proc isSymChoice(n: PNode): bool {.inline.} =
result = n.kind in nkSymChoices

proc maybeLiftType(t: var PType, c: PContext, info: TLineInfo) =
# XXX: liftParamType started to perform addDecl
# we could do that instead in semTypeNode by snooping for added
Expand Down Expand Up @@ -350,10 +348,10 @@ proc semConv(c: PContext, n: PNode; flags: TExprFlags = {}, expectedType: PType
if n[1].kind == nkExprEqExpr and
targetType.skipTypes(abstractPtrs).kind == tyObject:
localError(c.config, n.info, "object construction uses ':', not '='")
var op = semExprWithType(c, n[1], flags * {efDetermineType})
if op.kind == nkClosedSymChoice and op.len > 0 and
op[0].sym.kind == skEnumField: # resolves overloadedable enums
op = ambiguousSymChoice(c, n, op)
var op = semExprWithType(c, n[1], flags * {efDetermineType} + {efAllowSymChoice})
if isSymChoice(op) and op[0].sym.kind notin routineKinds:
# T(foo) disambiguation syntax only allowed for routines
op = semSymChoice(c, op)
if targetType.kind != tyGenericParam and targetType.isMetaType:
let final = inferWithMetatype(c, targetType, op, true)
result.add final
Expand Down Expand Up @@ -1074,7 +1072,7 @@ proc semIndirectOp(c: PContext, n: PNode, flags: TExprFlags; expectedType: PType
else:
n[0] = n0
else:
n[0] = semExpr(c, n[0], {efInCall})
n[0] = semExpr(c, n[0], {efInCall, efAllowSymChoice})
let t = n[0].typ
if t != nil and t.kind in {tyVar, tyLent}:
n[0] = newDeref(n[0])
Expand Down Expand Up @@ -1479,7 +1477,8 @@ proc builtinFieldAccess(c: PContext; n: PNode; flags: var TExprFlags): PNode =
onUse(n[1].info, s)
return

n[0] = semExprWithType(c, n[0], flags+{efDetermineType, efWantIterable})
# extra flags since LHS may become a call operand:
n[0] = semExprWithType(c, n[0], flags+{efDetermineType, efWantIterable, efAllowSymChoice})
#restoreOldStyleType(n[0])
var i = considerQuotedIdent(c, n[1], n)
var ty = n[0].typ
Expand Down Expand Up @@ -1633,7 +1632,7 @@ proc semSubscript(c: PContext, n: PNode, flags: TExprFlags): PNode =
return
checkMinSonsLen(n, 2, c.config)
# signal that generic parameters may be applied after
n[0] = semExprWithType(c, n[0], {efNoEvaluateGeneric})
n[0] = semExprWithType(c, n[0], {efNoEvaluateGeneric, efAllowSymChoice})
var arr = skipTypes(n[0].typ, {tyGenericInst, tyUserTypeClassInst, tyOwned,
tyVar, tyLent, tyPtr, tyRef, tyAlias, tySink})
if arr.kind == tyStatic:
Expand Down Expand Up @@ -1732,7 +1731,7 @@ proc propertyWriteAccess(c: PContext, n, nOrig, a: PNode): PNode =
# this is ugly. XXX Semantic checking should use the ``nfSem`` flag for
# nodes?
let aOrig = nOrig[0]
result = newTreeI(nkCall, n.info, setterId, a[0], semExprWithType(c, n[1]))
result = newTreeI(nkCall, n.info, setterId, a[0], n[1])
result.flags.incl nfDotSetter
let orig = newTreeI(nkCall, n.info, setterId, aOrig[0], nOrig[1])
result = semOverloadedCallAnalyseEffects(c, result, orig, {})
Expand Down Expand Up @@ -3087,10 +3086,10 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}, expectedType: PType
result = enumFieldSymChoice(c, n, s, flags)
else:
result = semSym(c, n, s, flags)
if expectedType != nil and isSymChoice(result):
result = fitNode(c, expectedType, result, n.info)
if result.kind == nkSym:
result = semSym(c, result, result.sym, flags)
if isSymChoice(result):
result = semSymChoice(c, result, flags, expectedType)
of nkClosedSymChoice, nkOpenSymChoice:
result = semSymChoice(c, result, flags, expectedType)
of nkSym:
let s = n.sym
if nfOpenSym in n.flags:
Expand Down Expand Up @@ -3313,10 +3312,6 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}, expectedType: PType
considerGenSyms(c, n)
of nkTableConstr:
result = semTableConstr(c, n, expectedType)
of nkClosedSymChoice, nkOpenSymChoice:
# handling of sym choices is context dependent
# the node is left intact for now
discard
of nkStaticExpr: result = semStaticExpr(c, n[0], expectedType)
of nkAsgn, nkFastAsgn: result = semAsgn(c, n)
of nkBlockStmt, nkBlockExpr: result = semBlock(c, n, flags, expectedType)
Expand Down
4 changes: 3 additions & 1 deletion compiler/seminst.nim
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,9 @@ proc instantiateProcType(c: PContext, pt: TIdTable,
for i in 1..<def.len:
def[i] = replaceTypeVarsN(cl, def[i], 1)

def = semExprWithType(c, def)
# allow symchoice since node will be fit later
# although expectedType should cover it
def = semExprWithType(c, def, {efAllowSymChoice}, typeToFit)
if def.referencesAnotherParam(getCurrOwner(c)):
def.flags.incl nfDefaultRefsParam

Expand Down
2 changes: 1 addition & 1 deletion compiler/semtypes.nim
Original file line number Diff line number Diff line change
Expand Up @@ -1329,7 +1329,7 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode,
def.typ = makeTypeFromExpr(c, def.copyTree)
break determineType

def = semExprWithType(c, def, {efDetermineType}, defTyp)
def = semExprWithType(c, def, {efDetermineType, efAllowSymChoice}, defTyp)
if def.referencesAnotherParam(getCurrOwner(c)):
def.flags.incl nfDefaultRefsParam

Expand Down
5 changes: 5 additions & 0 deletions config/nim.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ cc = gcc
# not needed if hint is a style check
hint[AmbiguousEnum]=off
@end

@if not nimHasNolineTooLong:
hint[LineTooLong]=off
@end

#hint[XDeclaredButNotUsed]=off

threads:on
Expand Down
2 changes: 1 addition & 1 deletion tests/enum/tambiguousoverloads.nim
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ block: # bug #21887
EnumC = enum C

doAssert typeof(EnumC(A)) is EnumC #[tt.Error
^ ambiguous identifier 'A' -- use one of the following:
^ ambiguous identifier 'A' -- use one of the following:
EnumA.A: EnumA
EnumB.A: EnumB]#

Expand Down
3 changes: 2 additions & 1 deletion tests/errmsgs/t8064.nim
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ values


discard """
errormsg: "expression has no type: values"
# either this or "expression has no type":
errormsg: "ambiguous identifier 'values' -- use one of the following:"
"""
2 changes: 1 addition & 1 deletion tests/lookups/tambprocvar.nim
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ block:

block:
let x = `+` #[tt.Error
^ ambiguous identifier '+' -- use one of the following:]#
^ ambiguous identifier '+' -- use one of the following:]#
10 changes: 10 additions & 0 deletions tests/specialops/tsetter.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
block: # ensure RHS of setter statement is treated as call operand
proc `b=`(a: var int, c: proc (x: int): int) =
a = c(a)

proc foo(x: int): int = x + 1
proc foo(x: float): float = x - 1

var a = 123
a.b = foo
doAssert a == 124

0 comments on commit b479462

Please sign in to comment.