Compare commits

...

14 Commits

Author SHA1 Message Date
ringabout
1204f4a38e more test cases 2023-03-31 16:38:19 +08:00
ringabout
a891b0d948 adds documentation 2023-03-30 23:02:05 +08:00
ringabout
a8c9e9c4e9 deduplicate 2023-03-29 21:12:01 +08:00
ringabout
d997bd4cb5 message 2023-03-26 23:45:37 +08:00
ringabout
e51f27c806 add one more message 2023-03-26 23:44:04 +08:00
ringabout
c1c53bebf1 fixes private fields 2023-03-25 15:47:38 +08:00
ringabout
bde9abb2f7 fixes tests 2023-03-25 09:45:01 +08:00
ringabout
3c36fc17dc Merge remote-tracking branch 'upstream/devel' into pr_object 2023-03-24 23:29:14 +08:00
ringabout
6f8b9b7cc4 better error messages 2023-03-24 23:24:37 +08:00
ringabout
a56697b513 add a filter for unnamed fields 2023-03-24 14:59:44 +08:00
ringabout
24857a0162 fixes a test 2023-03-23 23:06:31 +08:00
ringabout
91dee1bcff typo 2023-03-23 21:49:46 +08:00
ringabout
251fec94ce add some tests 2023-03-23 21:47:17 +08:00
ringabout
0175be50a9 fixes existing tests 2023-03-23 21:08:35 +08:00
11 changed files with 293 additions and 24 deletions

View File

@@ -3078,7 +3078,7 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}, expectedType: PType
result = semConv(c, n, expectedType)
elif ambig and n.len == 1:
errorUseQualifier(c, n.info, s)
elif n.len == 1:
elif n.len == 1 or (n.kind == nkCall and useObjConstr(c, n, flags, expectedType)):
result = semObjConstr(c, n, flags, expectedType)
elif s.magic == mNone: result = semDirectOp(c, n, flags, expectedType)
else: result = semMagic(c, n, s, flags, expectedType)

View File

@@ -506,12 +506,12 @@ proc semGenericStmt(c: PContext, n: PNode,
result[i] = semGenericStmt(c, n[i], flags, ctx)
if result[0].kind == nkSym:
let fmoduleId = getModule(result[0].sym).id
var isVisable = false
var isVisible = false
for module in c.friendModules:
if module.id == fmoduleId:
isVisable = true
isVisible = true
break
if isVisable:
if isVisible:
for i in 1..<result.len:
if result[i].kind == nkExprColonExpr:
result[i][1].flags.incl nfSkipFieldChecking

View File

@@ -412,15 +412,113 @@ proc defaultConstructionError(c: PContext, t: PType, info: TLineInfo) =
else:
assert false, "Must not enter here."
type
ObjConstrError = enum
none
discriminatorError = "The discriminator can only be initialized with unnamed fields known at the compile time"
mixingError = "When mixing named fields and unnamed fields, every field needs to be initialized in order"
lackingError = "The object construction is given more fields than required"
proc filterObjConstr(c: PContext; field: PNode, n: PNode, iterField: var int, flags: TExprFlags, write: bool): ObjConstrError =
result = none
if iterField >= n.len:
return mixingError
case field.kind
of nkRecCase:
# handle defaults if the ast of the field is known
var discriminatorVal =
case n[iterField].kind
of nkExprColonExpr:
semExprFlagDispatched(c, n[iterField][1], flags + {efPreferStatic})
else:
semExprFlagDispatched(c, n[iterField], flags + {efPreferStatic})
let ret = filterObjConstr(c, field[0], n, iterField, flags, write)
if ret != none:
return ret
if discriminatorVal == nil or discriminatorVal.kind != nkIntLit:
return discriminatorError
let matchedBranch = field.pickCaseBranch discriminatorVal
if matchedBranch != nil:
result = filterObjConstr(c, matchedBranch.lastSon, n, iterField, flags, write)
else:
result = none
of nkSym:
if n[iterField].kind == nkExprColonExpr and field.sym.name.id == considerQuotedIdent(c, n[iterField][0]).id:
inc iterField
elif not fieldVisible(c, field.sym):
discard
elif n[iterField].kind != nkExprColonExpr:
if write:
n[iterField] = newTree(nkExprColonExpr, field, n[iterField])
inc iterField
else:
result = mixingError
of nkRecList:
for f in field:
let ret = filterObjConstr(c, f, n, iterField, flags, write)
if ret != none:
result = ret
break
else:
assert false
proc expandObjConstr(c: PContext, n: PNode, t: PType, flags: TExprFlags): PNode =
result = n
var hasValue = false
for i in 1..<n.len:
if n[i].kind != nkExprColonExpr:
hasValue = true
break
if hasValue:
var iterField = 1
let ret = filterObjConstr(c, t.n, result, iterField, flags, write = true)
if ret != none:
localError(c.config, result.info, $ret)
else:
if iterField > result.len:
localError(c.config, result.info, $mixingError)
elif iterField < result.len:
localError(c.config, result.info, $lackingError)
proc useObjConstr(c: PContext, n: PNode, flags: TExprFlags; expectedType: PType = nil): bool =
var n = copyTree(n)
var t = semTypeNode(c, n[0], nil)
if t == nil:
return false
if t.skipTypes({tyGenericInst,
tyAlias, tySink, tyOwned, tyRef}).kind != tyObject and
expectedType != nil and expectedType.skipTypes({tyGenericInst,
tyAlias, tySink, tyOwned, tyRef}).kind == tyObject:
t = expectedType
t = skipTypes(t, {tyGenericInst, tyAlias, tySink, tyOwned})
if t.kind == tyRef:
t = skipTypes(t[0], {tyGenericInst, tyAlias, tySink, tyOwned})
if t.kind != tyObject:
return false
for i in 1..<n.len:
if n[i].kind == nkExprColonExpr:
return true
var iterField = 1
result = filterObjConstr(c, t.n, n, iterField, flags, write = false) == none
if iterField != n.len:
return false
proc semObjConstr(c: PContext, n: PNode, flags: TExprFlags; expectedType: PType = nil): PNode =
var t = semTypeNode(c, n[0], nil)
result = newNodeIT(nkObjConstr, n.info, t)
for i in 0..<n.len:
result.add n[i]
if t == nil:
return localErrorNode(c, result, "object constructor needs an object type")
if t.skipTypes({tyGenericInst,
tyAlias, tySink, tyOwned, tyRef}).kind != tyObject and
expectedType != nil and expectedType.skipTypes({tyGenericInst,
@@ -443,6 +541,10 @@ proc semObjConstr(c: PContext, n: PNode, flags: TExprFlags; expectedType: PType
"'; the object's generic parameters cannot be inferred and must be explicitly given"
)
let expanded = expandObjConstr(c, n, t, flags)
for i in 0..<expanded.len:
result.add expanded[i]
# Check if the object is fully initialized by recursively testing each
# field (if this is a case object, initialized fields in two different
# branches will be reported as an error):

View File

@@ -1828,6 +1828,23 @@ an `object` type or a `ref object` type:
Note that, unlike tuples, objects require the field names along with their values.
For a `ref object` type `system.new` is invoked implicitly.
The field names can be omitted if all the values are given in order. It can be mixed with field names along with values.
```nim
var a1 = Student("Anton", 5)
var a2 = PStudent("Anton", age: 5)
```
Note that, objects with only one field must use field names along with values. Otherwise, they will be recognized as type conversions.
```nim
type
Teacher = object
name: string
# var t = Teacher("lisa") # Error: type mismatch: got 'string' for '"lisa"'
# but expected 'Teacher = object'
var t = Teacher(name: "lisa")
```
Object variants
---------------

View File

@@ -1,5 +1,5 @@
discard """
errormsg: "incorrect object construction syntax"
errormsg: "When mixing named fields and unnamed fields, every field needs to be initialized in order"
file: "t5965_1.nim"
line: 10
"""

View File

@@ -1,5 +1,5 @@
discard """
errormsg: "incorrect object construction syntax"
errormsg: "The object construction is given more fields than required"
file: "t5965_2.nim"
line: 10
"""

View File

@@ -1,6 +1,25 @@
type
Noice* = object
hidden: int
template jjj*: Noice =
Noice(hidden: 15)
Ciao* = object
hidden1: int
hidden2: int
Gull* = ref object
hidden1: int
field*: int
hidden2: int
field2*: int
template jjj*(): Noice =
var x = 7
Noice(hidden: 15)
template said*(): Ciao =
var x = 7
Ciao(hidden1: 15 + x, 1)
proc foo*: Gull =
result = Gull(1, 2, 3, 4)

View File

@@ -3,7 +3,19 @@ import m3770
doAssert $jjj() == "(hidden: 15)" # works
doAssert $said() == "(hidden1: 22, hidden2: 1)"
proc someGeneric(_: type) =
doAssert $jjj() == "(hidden: 15)" # fails: "Error: the field 'hidden' is not accessible."
when false: # todo somehow make it work?
doAssert $said() == "(hidden1: 22, hidden2: 1)"
someGeneric(int)
doAssert $(foo()[]) == "(hidden1: 1, field: 2, hidden2: 3, field2: 4)"
proc bar() =
var s = Gull(13, 14)
doAssert $(s[]) == "(hidden1: 0, field: 13, hidden2: 0, field2: 14)"
bar()

View File

@@ -1,15 +1,3 @@
discard """
cmd: "nim check $file"
errormsg: ""
nimout: '''
t17437.nim(20, 16) Error: undeclared identifier: 'x'
t17437.nim(20, 16) Error: expression 'x' has no type (or is ambiguous)
t17437.nim(20, 19) Error: incorrect object construction syntax
t17437.nim(20, 19) Error: incorrect object construction syntax
t17437.nim(20, 12) Error: expression '' has no type (or is ambiguous)
'''
"""
# bug #17437 invalid object construction should result in error
type
@@ -17,6 +5,8 @@ type
x, y: int
proc m =
var x = 12
var y = 1
var v = V(x: x, y)
m()

View File

@@ -0,0 +1,39 @@
type
Standard* = object
name: string
id*: int
owner*: string
Color1* = enum
Red, Blue, Green
Case1* = object
name*: string
id*: int
color*: Color1
owner: string
## inplace object construction works
doAssert Standard("Tree", 1, "sky") == Standard(name: "Tree", id: 1, owner: "sky")
proc initStandard*(name: string, id: int, owner: string): Standard =
Standard(name, id, owner)
## It works in the procs
doAssert initStandard("Tree", 1, "sky") == Standard(name: "Tree", id: 1, owner: "sky")
static: doAssert initStandard("Tree", 1, "sky") == Standard(name: "Tree", id: 1, owner: "sky")
template toStandard*(name: string, id: int, owner: string): Standard =
Standard(name, id, owner)
## It works in the procs
doAssert toStandard("Tree", 1, "sky") == Standard(name: "Tree", id: 1, owner: "sky")
static: doAssert toStandard("Tree", 1, "sky") == Standard(name: "Tree", id: 1, owner: "sky")
proc initColorRed*(name: string = "red", id: int = 1314, owner: string): Case1 =
result = Case1(name, id, Red, owner)
doAssert Case1("red", 1314, color: Red, owner: "unknown") == Case1("red", 1314, color: Red, "unknown")
doAssert Case1("red", 1314, Red, owner: "unknown") == Case1("red", 1314, Red, "unknown")
doAssert initColorRed(owner = "unknown") == Case1("red", id: 1314, Red, "unknown")

View File

@@ -0,0 +1,90 @@
import mobjectconstr_unnamed
type
Vector = object
a: int = 999
b, c: int
block: # positional construction
## It specifies all the unnamed fields
var x = Vector(1, 2, 3)
doAssert x.b == 2
block:
## unnamed fields can be mixed with named fields
block:
var x = Vector(a: 1, 2, 3)
doAssert x.c == 3
block:
var x = Vector(1, b: 2, 3)
doAssert x.c == 3
block:
var x = Vector(1, 2, c: 3)
doAssert x.c == 3
block:
## Object variants support unnamed fields for tags, which should be known at the compile time.
type
Color = enum
Red, Blue, Yellow
Factor = object
id: int
case flag: Color
of Red:
num: int
of Blue, Yellow:
done: bool
name: string
block:
var x = Factor(1, Red, 2, "1314")
doAssert x.num == 2
block:
var x = Factor(1, Blue, true, "1314")
doAssert x.done == true
block:
var x = Factor(1, Yellow, false, "1314")
doAssert x.done == false
type
Ciao = object
id: int
case flag: bool = false
of true:
num: int
of false:
done: bool
name: string
block:
var x = Ciao(12, false, false, "123")
doAssert x.done == false
block:
var x = Ciao(12, flag: true, 1, "123")
doAssert x.num == 1
## It works in the third module
block:
doAssert initStandard("", 1, "sky") == Standard(id: 1, owner: "sky")
doAssert initStandard("", 1, "sky") == Standard(1, "sky")
doAssert toStandard("", 1, "sky") == Standard(1, "sky")
proc foo() =
doAssert initStandard("", 1, "sky") == Standard(id: 1, owner: "sky")
doAssert initStandard("", 1, "sky") == Standard(1, "sky")
doAssert toStandard("", 1, "sky") == Standard(1, "sky")
foo()
template bar() =
doAssert initStandard("", 1, "sky") == Standard(id: 1, owner: "sky")
doAssert initStandard("", 1, "sky") == Standard(1, "sky")
doAssert toStandard("", 1, "sky") == Standard(1, "sky")
bar()