make expressions opt in to symchoices (#22716)

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 5f9038a5d7)
This commit is contained in:
metagn
2023-09-18 07:39:22 +03:00
committed by narimiran
parent 7b834b94da
commit 8a4ee2b84f
11 changed files with 66 additions and 64 deletions

View File

@@ -143,7 +143,6 @@ proc initDefines*(symbols: StringTableRef) =
defineSymbol("nimHasTemplateRedefinitionPragma")
defineSymbol("nimHasCstringCase")
defineSymbol("nimHasCallsitePragma")
defineSymbol("nimHasAmbiguousEnumHint")
defineSymbol("nimHasWarnCastSizes") # deadcode
defineSymbol("nimHasOutParams")

View File

@@ -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
@@ -228,7 +227,6 @@ const
hintGCStats: "$1",
hintGlobalVar: "global variable declared here",
hintExpandMacro: "expanded macro: $1",
hintAmbiguousEnum: "$1",
hintUser: "$1",
hintUserRaw: "$1",
hintExtendedContext: "$1",

View File

@@ -76,6 +76,7 @@ type
efNoDiagnostics,
efTypeAllowed # typeAllowed will be called after
efWantNoDefaults
efAllowSymChoice # symchoice node should not be resolved
TExprFlags* = set[TExprFlag]

View File

@@ -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 != {}:
@@ -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}))
@@ -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:
@@ -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
@@ -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
@@ -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])
@@ -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
@@ -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:
@@ -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, {})
@@ -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:
@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -14,10 +14,6 @@ cc = gcc
# additional options always passed to the compiler:
--parallel_build: "0" # 0 to auto-detect number of processors
@if nimHasAmbiguousEnumHint:
# not needed if hint is a style check
hint[AmbiguousEnum]=off
@end
#hint[XDeclaredButNotUsed]=off
threads:on

View File

@@ -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]#

View File

@@ -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:"
"""

View File

@@ -16,4 +16,4 @@ block:
block:
let x = `+` #[tt.Error
^ ambiguous identifier '+' -- use one of the following:]#
^ ambiguous identifier '+' -- use one of the following:]#

View File

@@ -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