Consider range type of runtime discrim [feature] (#11432)
This commit is contained in:
parent
d00c8febee
commit
4264e9576d
3 changed files with 99 additions and 17 deletions
|
|
@ -113,6 +113,13 @@ proc branchVals(c: PContext, caseNode: PNode, caseIdx: int,
|
||||||
for i in 1 .. caseNode.len-2:
|
for i in 1 .. caseNode.len-2:
|
||||||
processBranchVals(caseNode[i], excl)
|
processBranchVals(caseNode[i], excl)
|
||||||
|
|
||||||
|
proc rangeTypVals(rangeTyp: PType): IntSet =
|
||||||
|
assert rangeTyp.kind == tyRange
|
||||||
|
let (a, b) = (rangeTyp.n.sons[0].intVal, rangeTyp.n.sons[1].intVal)
|
||||||
|
result = initIntSet()
|
||||||
|
for it in a .. b:
|
||||||
|
result.incl(it.int)
|
||||||
|
|
||||||
proc formatUnsafeBranchVals(t: PType, diffVals: IntSet): string =
|
proc formatUnsafeBranchVals(t: PType, diffVals: IntSet): string =
|
||||||
if diffVals.len <= 32:
|
if diffVals.len <= 32:
|
||||||
var strs: seq[string]
|
var strs: seq[string]
|
||||||
|
|
@ -228,9 +235,9 @@ proc semConstructFields(c: PContext, recNode: PNode,
|
||||||
template badDiscriminatorError =
|
template badDiscriminatorError =
|
||||||
let fields = fieldsPresentInBranch(selectedBranch)
|
let fields = fieldsPresentInBranch(selectedBranch)
|
||||||
localError(c.config, initExpr.info,
|
localError(c.config, initExpr.info,
|
||||||
("you must provide a compile-time value for the discriminator '$1' " &
|
("cannot prove that it's safe to initialize $1 with " &
|
||||||
"in order to prove that it's safe to initialize $2.") %
|
"the runtime value for the discriminator '$2' ") %
|
||||||
[discriminator.sym.name.s, fields])
|
[fields, discriminator.sym.name.s])
|
||||||
mergeInitStatus(result, initNone)
|
mergeInitStatus(result, initNone)
|
||||||
|
|
||||||
template wrongBranchError(i) =
|
template wrongBranchError(i) =
|
||||||
|
|
@ -241,30 +248,45 @@ proc semConstructFields(c: PContext, recNode: PNode,
|
||||||
"are in conflict with this value.",
|
"are in conflict with this value.",
|
||||||
[discriminator.sym.name.s, discriminatorVal.renderTree, fields])
|
[discriminator.sym.name.s, discriminatorVal.renderTree, fields])
|
||||||
|
|
||||||
|
template valuesInConflictError(valsDiff) =
|
||||||
|
localError(c.config, discriminatorVal.info, ("possible values " &
|
||||||
|
"$2are in conflict with discriminator values for " &
|
||||||
|
"selected object branch $1.") % [$selectedBranch,
|
||||||
|
formatUnsafeBranchVals(recNode.sons[0].typ, valsDiff)])
|
||||||
|
|
||||||
let branchNode = recNode[selectedBranch]
|
let branchNode = recNode[selectedBranch]
|
||||||
let flags = flags*{efAllowDestructor} + {efPreferStatic,
|
let flags = flags*{efAllowDestructor} + {efPreferStatic,
|
||||||
efPreferNilResult}
|
efPreferNilResult}
|
||||||
var discriminatorVal = semConstrField(c, flags,
|
var discriminatorVal = semConstrField(c, flags,
|
||||||
discriminator.sym, initExpr)
|
discriminator.sym, initExpr)
|
||||||
|
|
||||||
if discriminatorVal != nil:
|
if discriminatorVal != nil:
|
||||||
discriminatorVal = discriminatorVal.skipHidden
|
discriminatorVal = discriminatorVal.skipHidden
|
||||||
|
if discriminatorVal.kind notin nkLiterals and (
|
||||||
|
not isOrdinalType(discriminatorVal.typ, true) or
|
||||||
|
lengthOrd(c.config, discriminatorVal.typ) > MaxSetElements or
|
||||||
|
lengthOrd(c.config, recNode.sons[0].typ) > MaxSetElements):
|
||||||
|
localError(c.config, discriminatorVal.info,
|
||||||
|
"branch initialization with a runtime discriminator only " &
|
||||||
|
"supports ordinal types with 2^16 elements or less.")
|
||||||
|
|
||||||
if discriminatorVal == nil:
|
if discriminatorVal == nil:
|
||||||
badDiscriminatorError()
|
badDiscriminatorError()
|
||||||
elif discriminatorVal.kind == nkSym:
|
elif discriminatorVal.kind == nkSym:
|
||||||
let (ctorCase, ctorIdx) = findUsefulCaseContext(c, discriminatorVal)
|
let (ctorCase, ctorIdx) = findUsefulCaseContext(c, discriminatorVal)
|
||||||
if ctorCase == nil:
|
if ctorCase == nil:
|
||||||
|
if discriminatorVal.typ.kind == tyRange:
|
||||||
|
let rangeVals = rangeTypVals(discriminatorVal.typ)
|
||||||
|
let recBranchVals = branchVals(c, recNode, selectedBranch, false)
|
||||||
|
let diff = rangeVals - recBranchVals
|
||||||
|
if diff.len != 0:
|
||||||
|
valuesInConflictError(diff)
|
||||||
|
else:
|
||||||
badDiscriminatorError()
|
badDiscriminatorError()
|
||||||
elif discriminatorVal.sym.kind notin {skLet, skParam} or
|
elif discriminatorVal.sym.kind notin {skLet, skParam} or
|
||||||
discriminatorVal.sym.typ.kind == tyVar:
|
discriminatorVal.sym.typ.kind == tyVar:
|
||||||
localError(c.config, discriminatorVal.info,
|
localError(c.config, discriminatorVal.info,
|
||||||
"runtime discriminator must be immutable if branch fields are " &
|
"runtime discriminator must be immutable if branch fields are " &
|
||||||
"initialized, a 'let' binding is required.")
|
"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:
|
elif ctorCase[ctorIdx].kind == nkElifBranch:
|
||||||
localError(c.config, discriminatorVal.info, "branch initialization " &
|
localError(c.config, discriminatorVal.info, "branch initialization " &
|
||||||
"with a runtime discriminator is not supported inside of an " &
|
"with a runtime discriminator is not supported inside of an " &
|
||||||
|
|
@ -275,20 +297,27 @@ proc semConstructFields(c: PContext, recNode: PNode,
|
||||||
recBranchVals = branchVals(c, recNode, selectedBranch, false)
|
recBranchVals = branchVals(c, recNode, selectedBranch, false)
|
||||||
branchValsDiff = ctorBranchVals - recBranchVals
|
branchValsDiff = ctorBranchVals - recBranchVals
|
||||||
if branchValsDiff.len != 0:
|
if branchValsDiff.len != 0:
|
||||||
localError(c.config, discriminatorVal.info, ("possible values " &
|
valuesInConflictError(branchValsDiff)
|
||||||
"$2are in conflict with discriminator values for " &
|
|
||||||
"selected object branch $1.") % [$selectedBranch,
|
|
||||||
formatUnsafeBranchVals(recNode.sons[0].typ, branchValsDiff)])
|
|
||||||
else:
|
else:
|
||||||
|
var failedBranch = -1
|
||||||
if branchNode.kind != nkElse:
|
if branchNode.kind != nkElse:
|
||||||
if not branchNode.caseBranchMatchesExpr(discriminatorVal):
|
if not branchNode.caseBranchMatchesExpr(discriminatorVal):
|
||||||
wrongBranchError(selectedBranch)
|
failedBranch = selectedBranch
|
||||||
else:
|
else:
|
||||||
# With an else clause, check that all other branches don't match:
|
# With an else clause, check that all other branches don't match:
|
||||||
for i in 1 .. (recNode.len - 2):
|
for i in 1 .. (recNode.len - 2):
|
||||||
if recNode[i].caseBranchMatchesExpr(discriminatorVal):
|
if recNode[i].caseBranchMatchesExpr(discriminatorVal):
|
||||||
wrongBranchError(i)
|
failedBranch = i
|
||||||
break
|
break
|
||||||
|
if failedBranch != -1:
|
||||||
|
if discriminatorVal.typ.kind == tyRange:
|
||||||
|
let rangeVals = rangeTypVals(discriminatorVal.typ)
|
||||||
|
let recBranchVals = branchVals(c, recNode, selectedBranch, false)
|
||||||
|
let diff = rangeVals - recBranchVals
|
||||||
|
if diff.len != 0:
|
||||||
|
valuesInConflictError(diff)
|
||||||
|
else:
|
||||||
|
wrongBranchError(failedBranch)
|
||||||
|
|
||||||
# When a branch is selected with a partial match, some of the fields
|
# When a branch is selected with a partial match, some of the fields
|
||||||
# that were not initialized may be mandatory. We must check for this:
|
# that were not initialized may be mandatory. We must check for this:
|
||||||
|
|
|
||||||
|
|
@ -1600,7 +1600,9 @@ statement. If possible values of the discriminator variable in a
|
||||||
``case`` statement branch are a subset of discriminator values for the selected
|
``case`` statement branch are a subset of discriminator values for the selected
|
||||||
object branch, the initialization is considered valid. This analysis only works
|
object branch, the initialization is considered valid. This analysis only works
|
||||||
for immutable discriminators of an ordinal type and disregards ``elif``
|
for immutable discriminators of an ordinal type and disregards ``elif``
|
||||||
branches.
|
branches. For discriminator values with a ``range`` type, the compiler
|
||||||
|
checks if the entire range of possible values for the discriminator value is
|
||||||
|
valid for the choosen object branch.
|
||||||
|
|
||||||
A small example:
|
A small example:
|
||||||
|
|
||||||
|
|
@ -1619,6 +1621,10 @@ A small example:
|
||||||
else:
|
else:
|
||||||
echo "ignoring: ", unknownKind
|
echo "ignoring: ", unknownKind
|
||||||
|
|
||||||
|
# also valid, since unknownKindBounded can only contain the values nkAdd or nkSub
|
||||||
|
let unknownKindBounded = range[nkAdd..nkSub](unknownKind)
|
||||||
|
z = Node(kind: unknownKindBounded, leftOp: Node(), rightOp: Node())
|
||||||
|
|
||||||
Set type
|
Set type
|
||||||
--------
|
--------
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -148,3 +148,50 @@ reject:
|
||||||
case kind:
|
case kind:
|
||||||
of k1: result = KindObj(kind: kind, i32: 1)
|
of k1: result = KindObj(kind: kind, i32: 1)
|
||||||
else: discard
|
else: discard
|
||||||
|
|
||||||
|
type
|
||||||
|
Kind3 = enum
|
||||||
|
A, B, C, E
|
||||||
|
|
||||||
|
OkRange = range[B..C]
|
||||||
|
NotOkRange = range[B..E]
|
||||||
|
|
||||||
|
CaseObject = object
|
||||||
|
case kind: Kind3
|
||||||
|
of B, C:
|
||||||
|
field: int
|
||||||
|
else: discard
|
||||||
|
|
||||||
|
accept:
|
||||||
|
let rtDiscriminator: OkRange = B
|
||||||
|
discard CaseObject(kind: rtDiscriminator, field: 1)
|
||||||
|
|
||||||
|
accept:
|
||||||
|
let rtDiscriminator = B
|
||||||
|
discard CaseObject(kind: OkRange(rtDiscriminator), field: 1)
|
||||||
|
|
||||||
|
accept:
|
||||||
|
const rtDiscriminator: NotOkRange = B
|
||||||
|
discard CaseObject(kind: rtDiscriminator, field: 1)
|
||||||
|
|
||||||
|
accept:
|
||||||
|
discard CaseObject(kind: NotOkRange(B), field: 1)
|
||||||
|
|
||||||
|
reject:
|
||||||
|
let rtDiscriminator: NotOkRange = B
|
||||||
|
discard CaseObject(kind: rtDiscriminator, field: 1)
|
||||||
|
|
||||||
|
reject:
|
||||||
|
let rtDiscriminator = B
|
||||||
|
discard CaseObject(kind: NotOkRange(rtDiscriminator), field: 1)
|
||||||
|
|
||||||
|
reject:
|
||||||
|
type Obj = object
|
||||||
|
case x: int
|
||||||
|
of 0 .. 1000:
|
||||||
|
field: int
|
||||||
|
else:
|
||||||
|
discard
|
||||||
|
|
||||||
|
let x: range[0..15] = 1
|
||||||
|
let o = Obj(x: x, field: 1)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue