mirror of
https://github.com/nim-lang/Nim.git
synced 2026-04-18 21:40:32 +00:00
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:
@@ -143,7 +143,6 @@ proc initDefines*(symbols: StringTableRef) =
|
||||
defineSymbol("nimHasTemplateRedefinitionPragma")
|
||||
defineSymbol("nimHasCstringCase")
|
||||
defineSymbol("nimHasCallsitePragma")
|
||||
defineSymbol("nimHasAmbiguousEnumHint")
|
||||
|
||||
defineSymbol("nimHasWarnCastSizes") # deadcode
|
||||
defineSymbol("nimHasOutParams")
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -76,6 +76,7 @@ type
|
||||
efNoDiagnostics,
|
||||
efTypeAllowed # typeAllowed will be called after
|
||||
efWantNoDefaults
|
||||
efAllowSymChoice # symchoice node should not be resolved
|
||||
|
||||
TExprFlags* = set[TExprFlag]
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]#
|
||||
|
||||
|
||||
@@ -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:"
|
||||
"""
|
||||
|
||||
@@ -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
tests/specialops/tsetter.nim
Normal file
10
tests/specialops/tsetter.nim
Normal 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
|
||||
Reference in New Issue
Block a user