Smarter variant object construction (#11273)
This commit is contained in:
parent
16aa10dfe1
commit
f774426095
9 changed files with 333 additions and 15 deletions
|
|
@ -40,6 +40,7 @@ type
|
|||
wasForwarded*: bool # whether the current proc has a separate header
|
||||
mappingExists*: bool
|
||||
mapping*: TIdTable
|
||||
caseContext*: seq[tuple[n: PNode, idx: int]]
|
||||
|
||||
TMatchedConcept* = object
|
||||
candidateType*: PType
|
||||
|
|
@ -416,3 +417,12 @@ proc checkMinSonsLen*(n: PNode, length: int; conf: ConfigRef) =
|
|||
|
||||
proc isTopLevel*(c: PContext): bool {.inline.} =
|
||||
result = c.currentScope.depthLevel <= 2
|
||||
|
||||
proc pushCaseContext*(c: PContext, caseNode: PNode) =
|
||||
add(c.p.caseContext, (caseNode, 0))
|
||||
|
||||
proc popCaseContext*(c: PContext) =
|
||||
discard pop(c.p.caseContext)
|
||||
|
||||
proc setCaseContextIdx*(c: PContext, idx: int) =
|
||||
c.p.caseContext[^1].idx = idx
|
||||
|
|
|
|||
|
|
@ -84,6 +84,53 @@ proc caseBranchMatchesExpr(branch, matched: PNode): bool =
|
|||
|
||||
return false
|
||||
|
||||
template processBranchVals(b, op) =
|
||||
if b.kind != nkElifBranch:
|
||||
for i in 0 .. b.len-2:
|
||||
if b[i].kind == nkIntLit:
|
||||
result.op(b[i].intVal.int)
|
||||
elif b[i].kind == nkRange:
|
||||
for i in b[i][0].intVal .. b[i][1].intVal:
|
||||
result.op(i.int)
|
||||
|
||||
proc allPossibleValues(c: PContext, t: PType): IntSet =
|
||||
result = initIntSet()
|
||||
if t.kind == tyEnum:
|
||||
for field in t.n.sons:
|
||||
result.incl(field.sym.position)
|
||||
else:
|
||||
for i in firstOrd(c.config, t) .. lastOrd(c.config, t):
|
||||
result.incl(i.int)
|
||||
|
||||
proc branchVals(c: PContext, caseNode: PNode, caseIdx: int,
|
||||
isStmtBranch: bool): IntSet =
|
||||
if caseNode[caseIdx].kind == nkOfBranch:
|
||||
result = initIntSet()
|
||||
processBranchVals(caseNode[caseIdx], incl)
|
||||
else:
|
||||
result = allPossibleValues(c, caseNode.sons[0].typ)
|
||||
for i in 1 .. caseNode.len-2:
|
||||
processBranchVals(caseNode[i], excl)
|
||||
|
||||
proc formatUnsafeBranchVals(c: PContext, t: PType, diffVals: IntSet): string =
|
||||
if diffVals.len <= 32:
|
||||
var strs: seq[string]
|
||||
if t.kind == tyEnum:
|
||||
var i = 0
|
||||
for val in diffVals:
|
||||
while t.n.sons[i].sym.position < val: inc(i)
|
||||
strs.add(t.n.sons[i].sym.name.s)
|
||||
else:
|
||||
for val in diffVals:
|
||||
strs.add($val)
|
||||
result = "{" & strs.join(", ") & "} "
|
||||
|
||||
proc findUsefulCaseContext(c: PContext, discrimator: PNode): (PNode, int) =
|
||||
for i in countdown(c.p.caseContext.high, 0):
|
||||
let (caseNode, index) = c.p.caseContext[i]
|
||||
if caseNode[0].kind == nkSym and caseNode[0].sym == discrimator.sym:
|
||||
return (caseNode, index)
|
||||
|
||||
proc pickCaseBranch(caseExpr, matched: PNode): PNode =
|
||||
# XXX: Perhaps this proc already exists somewhere
|
||||
let endsWithElse = caseExpr[^1].kind == nkElse
|
||||
|
|
@ -173,28 +220,60 @@ proc semConstructFields(c: PContext, recNode: PNode,
|
|||
selectedBranch = i
|
||||
|
||||
if selectedBranch != -1:
|
||||
let branchNode = recNode[selectedBranch]
|
||||
let flags = flags*{efAllowDestructor} + {efNeedStatic, efPreferNilResult}
|
||||
let discriminatorVal = semConstrField(c, flags,
|
||||
discriminator.sym, initExpr)
|
||||
if discriminatorVal == nil:
|
||||
template badDiscriminatorError =
|
||||
let fields = fieldsPresentInBranch(selectedBranch)
|
||||
localError(c.config, initExpr.info,
|
||||
("you must provide a compile-time value for the discriminator '$1' " &
|
||||
"in order to prove that it's safe to initialize $2.") %
|
||||
[discriminator.sym.name.s, fields])
|
||||
mergeInitStatus(result, initNone)
|
||||
|
||||
template wrongBranchError(i) =
|
||||
let fields = fieldsPresentInBranch(i)
|
||||
localError(c.config, initExpr.info,
|
||||
"a case selecting discriminator '$1' with value '$2' " &
|
||||
"appears in the object construction, but the field(s) $3 " &
|
||||
"are in conflict with this value.",
|
||||
[discriminator.sym.name.s, discriminatorVal.renderTree, fields])
|
||||
|
||||
let branchNode = recNode[selectedBranch]
|
||||
let flags = flags*{efAllowDestructor} + {efPreferStatic,
|
||||
efPreferNilResult}
|
||||
var discriminatorVal = semConstrField(c, flags,
|
||||
discriminator.sym, initExpr)
|
||||
|
||||
if discriminatorVal != nil:
|
||||
discriminatorVal = discriminatorVal.skipHidden
|
||||
if discriminatorVal == nil:
|
||||
badDiscriminatorError()
|
||||
elif discriminatorVal.kind == nkSym:
|
||||
let (ctorCase, ctorIdx) = findUsefulCaseContext(c, discriminatorVal)
|
||||
if ctorCase == nil:
|
||||
badDiscriminatorError()
|
||||
elif discriminatorVal.sym.kind != skLet:
|
||||
localError(c.config, discriminatorVal.info,
|
||||
"runtime discriminator must be immutable if branch fields are " &
|
||||
"initialized, a 'let' binding is required.")
|
||||
elif not isOrdinalType(discriminatorVal.sym.typ, true) or
|
||||
lengthOrd(c.config, discriminatorVal.sym.typ) > MaxSetElements:
|
||||
localError(c.config, discriminatorVal.info,
|
||||
"branch initialization with a runtime discriminator only " &
|
||||
"supports ordinal types with 2^16 elements or less.")
|
||||
elif ctorCase[ctorIdx].kind == nkElifBranch:
|
||||
localError(c.config, discriminatorVal.info, "branch initialization " &
|
||||
"with a runtime discriminator is not supported inside of an " &
|
||||
"`elif` branch.")
|
||||
else:
|
||||
var
|
||||
ctorBranchVals = branchVals(c, ctorCase, ctorIdx, true)
|
||||
recBranchVals = branchVals(c, recNode, selectedBranch, false)
|
||||
branchValsDiff = ctorBranchVals - recBranchVals
|
||||
if branchValsDiff.len != 0:
|
||||
localError(c.config, discriminatorVal.info, ("possible values " &
|
||||
"$2are in conflict with discriminator values for " &
|
||||
"selected object branch $1.") % [$selectedBranch,
|
||||
formatUnsafeBranchVals(c, recNode.sons[0].typ, branchValsDiff)])
|
||||
else:
|
||||
let discriminatorVal = discriminatorVal.skipHidden
|
||||
|
||||
template wrongBranchError(i) =
|
||||
let fields = fieldsPresentInBranch(i)
|
||||
localError(c.config, initExpr.info,
|
||||
"a case selecting discriminator '$1' with value '$2' " &
|
||||
"appears in the object construction, but the field(s) $3 " &
|
||||
"are in conflict with this value.",
|
||||
[discriminator.sym.name.s, discriminatorVal.renderTree, fields])
|
||||
|
||||
if branchNode.kind != nkElse:
|
||||
if not branchNode.caseBranchMatchesExpr(discriminatorVal):
|
||||
wrongBranchError(selectedBranch)
|
||||
|
|
|
|||
|
|
@ -874,6 +874,7 @@ proc semCase(c: PContext, n: PNode; flags: TExprFlags): PNode =
|
|||
result = n
|
||||
checkMinSonsLen(n, 2, c.config)
|
||||
openScope(c)
|
||||
pushCaseContext(c, n)
|
||||
n.sons[0] = semExprWithType(c, n.sons[0])
|
||||
var chckCovered = false
|
||||
var covered: BiggestInt = 0
|
||||
|
|
@ -897,6 +898,7 @@ proc semCase(c: PContext, n: PNode; flags: TExprFlags): PNode =
|
|||
localError(c.config, n.sons[0].info, errSelectorMustBeOfCertainTypes)
|
||||
return
|
||||
for i in 1 ..< sonsLen(n):
|
||||
setCaseContextIdx(c, i)
|
||||
var x = n.sons[i]
|
||||
when defined(nimsuggest):
|
||||
if c.config.ideCmd == ideSug and exactEquals(c.config.m.trackPos, x.info) and caseTyp.kind == tyEnum:
|
||||
|
|
@ -934,6 +936,7 @@ proc semCase(c: PContext, n: PNode; flags: TExprFlags): PNode =
|
|||
formatMissingEnums(n))
|
||||
else:
|
||||
localError(c.config, n.info, "not all cases are covered")
|
||||
popCaseContext(c)
|
||||
closeScope(c)
|
||||
if isEmptyType(typ) or typ.kind in {tyNil, tyUntyped} or
|
||||
(not hasElse and efInTypeof notin flags):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue