Skip to content

Commit

Permalink
fix bugs with dot & call operators [backport] (#20931)
Browse files Browse the repository at this point in the history
* better error messages for dot operators [backport]

fixes #13063

* also fixes #7777

* fix #6981 and #9831 too

* fix

* minor improvement

* sus test fixes

* make test multiplatform lol

* fix nimsuggest test, extra improvements
  • Loading branch information
metagn authored Nov 28, 2022
1 parent f31dc63 commit 555c5ed
Show file tree
Hide file tree
Showing 6 changed files with 156 additions and 49 deletions.
58 changes: 33 additions & 25 deletions compiler/semcall.nim
Original file line number Diff line number Diff line change
Expand Up @@ -287,8 +287,23 @@ proc notFoundError*(c: PContext, n: PNode, errors: CandidateErrors) =
# fail fast:
globalError(c.config, n.info, "type mismatch")
return
# see getMsgDiagnostic:
if nfExplicitCall notin n.flags and {nfDotField, nfDotSetter} * n.flags != {}:
let ident = considerQuotedIdent(c, n[0], n).s
let sym = n[1].typ.typSym
var typeHint = ""
if sym == nil:
discard
else:
typeHint = " for type " & getProcHeader(c.config, sym)
localError(c.config, n.info, errUndeclaredField % ident & typeHint)
return
if errors.len == 0:
localError(c.config, n.info, "expression '$1' cannot be called" % n[0].renderTree)
if n[0].kind in nkIdentKinds:
let ident = considerQuotedIdent(c, n[0], n).s
localError(c.config, n.info, errUndeclaredRoutine % ident)
else:
localError(c.config, n.info, "expression '$1' cannot be called" % n[0].renderTree)
return

let (prefer, candidates) = presentFailedCandidates(c, n, errors)
Expand Down Expand Up @@ -330,7 +345,7 @@ proc getMsgDiagnostic(c: PContext, flags: TExprFlags, n, f: PNode): string =
sym = nextOverloadIter(o, c, f)

let ident = considerQuotedIdent(c, f, n).s
if {nfDotField, nfExplicitCall} * n.flags == {nfDotField}:
if nfExplicitCall notin n.flags and {nfDotField, nfDotSetter} * n.flags != {}:
let sym = n[1].typ.typSym
var typeHint = ""
if sym == nil:
Expand Down Expand Up @@ -363,11 +378,15 @@ proc resolveOverloads(c: PContext, n, orig: PNode,
else:
initialBinding = nil

template pickBest(headSymbol) =
pickBestCandidate(c, f, n, orig, initialBinding,
filter, result, alt, errors, efExplain in flags,
errorsEnabled, flags)

var dummyErrors: CandidateErrors
template pickSpecialOp(headSymbol) =
pickBestCandidate(c, headSymbol, n, orig, initialBinding,
filter, result, alt, errors, efExplain in flags,
errorsEnabled, flags)
pickBest(f)
filter, result, alt, dummyErrors, efExplain in flags,
false, flags)

let overloadsState = result.state
if overloadsState != csMatch:
Expand All @@ -383,7 +402,7 @@ proc resolveOverloads(c: PContext, n, orig: PNode,
let op = newIdentNode(getIdent(c.cache, x), n.info)
n[0] = op
orig[0] = op
pickBest(op)
pickSpecialOp(op)

if nfExplicitCall in n.flags:
tryOp ".()"
Expand All @@ -397,26 +416,15 @@ proc resolveOverloads(c: PContext, n, orig: PNode,
let callOp = newIdentNode(getIdent(c.cache, ".="), n.info)
n.sons[0..1] = [callOp, n[1], calleeName]
orig.sons[0..1] = [callOp, orig[1], calleeName]
pickBest(callOp)
pickSpecialOp(callOp)

if overloadsState == csEmpty and result.state == csEmpty:
if efNoUndeclared notin flags: # for tests/pragmas/tcustom_pragma.nim
template impl() =
result.state = csNoMatch
if efNoDiagnostics in flags:
return
# xxx adapt/use errorUndeclaredIdentifierHint(c, n, f.ident)
localError(c.config, n.info, getMsgDiagnostic(c, flags, n, f))
if n[0].kind == nkIdent and n[0].ident.s == ".=" and n[2].kind == nkIdent:
let sym = n[1].typ.sym
if sym == nil:
impl()
else:
let field = n[2].ident.s
let msg = errUndeclaredField % field & " for type " & getProcHeader(c.config, sym)
localError(c.config, orig[2].info, msg)
else:
impl()
result.state = csNoMatch
if efNoDiagnostics in flags:
return
# xxx adapt/use errorUndeclaredIdentifierHint(c, n, f.ident)
localError(c.config, n.info, getMsgDiagnostic(c, flags, n, f))
return
elif result.state != csMatch:
if nfExprCall in n.flags:
Expand Down Expand Up @@ -619,7 +627,7 @@ proc explicitGenericSym(c: PContext, n: PNode, s: PSym): PNode =
proc explicitGenericInstantiation(c: PContext, n: PNode, s: PSym): PNode =
assert n.kind == nkBracketExpr
for i in 1..<n.len:
let e = semExpr(c, n[i])
let e = semExprWithType(c, n[i])
if e.typ == nil:
n[i].typ = errorType(c)
else:
Expand Down
9 changes: 6 additions & 3 deletions compiler/semexprs.nim
Original file line number Diff line number Diff line change
Expand Up @@ -535,7 +535,7 @@ proc overloadedCallOpr(c: PContext, n: PNode): PNode =
result = newNodeI(nkCall, n.info)
result.add newIdentNode(par, n.info)
for i in 0..<n.len: result.add n[i]
result = semExpr(c, result)
result = semExpr(c, result, flags = {efNoUndeclared})

proc changeType(c: PContext; n: PNode, newType: PType, check: bool) =
case n.kind
Expand Down Expand Up @@ -993,6 +993,9 @@ proc semIndirectOp(c: PContext, n: PNode, flags: TExprFlags; expectedType: PType
if s != nil:
setGenericParams(c, n[0])
return semDirectOp(c, n, flags, expectedType)
elif isSymChoice(n[0]):
# overloaded generic procs e.g. newSeq[int] can end up here
return semDirectOp(c, n, flags, expectedType)

var t: PType = nil
if n[0].typ != nil:
Expand Down Expand Up @@ -1042,10 +1045,10 @@ proc semIndirectOp(c: PContext, n: PNode, flags: TExprFlags; expectedType: PType
instGenericConvertersSons(c, result, m)

else:
result = overloadedCallOpr(c, n)
result = overloadedCallOpr(c, n) # this uses efNoUndeclared
# Now that nkSym does not imply an iteration over the proc/iterator space,
# the old ``prc`` (which is likely an nkIdent) has to be restored:
if result == nil:
if result == nil or result.kind == nkEmpty:
# XXX: hmm, what kind of symbols will end up here?
# do we really need to try the overload resolution?
n[0] = prc
Expand Down
2 changes: 0 additions & 2 deletions nimsuggest/tests/tgeneric_highlight.nim
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,10 @@ $nimsuggest --tester $file
highlight;;skType;;1;;7;;3
highlight;;skProc;;1;;0;;6
highlight;;skProc;;1;;0;;6
highlight;;skType;;1;;7;;3
highlight;;skProc;;1;;0;;6
highlight;;skType;;2;;14;;3
highlight;;skProc;;2;;7;;6
highlight;;skProc;;2;;7;;6
highlight;;skType;;2;;14;;3
highlight;;skProc;;2;;7;;6
highlight;;skTemplate;;3;;0;;8
highlight;;skType;;3;;9;;3
Expand Down
38 changes: 19 additions & 19 deletions tests/concepts/texplain.nim
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,8 @@ proc e(o: ExplainedConcept): int
required type for o: ExplainedConcept
but expression '10' is of type: int literal(10)
texplain.nim(128, 6) ExplainedConcept: undeclared field: 'foo'
texplain.nim(128, 6) ExplainedConcept: undeclared field: '.'
texplain.nim(128, 6) ExplainedConcept: expression '.' cannot be called
texplain.nim(128, 6) ExplainedConcept: expression '' has no type (or is ambiguous)
texplain.nim(128, 5) ExplainedConcept: concept predicate failed
texplain.nim(129, 6) ExplainedConcept: undeclared field: 'bar'
texplain.nim(129, 6) ExplainedConcept: undeclared field: '.'
texplain.nim(129, 6) ExplainedConcept: expression '.' cannot be called
texplain.nim(129, 6) ExplainedConcept: expression '' has no type (or is ambiguous)
texplain.nim(128, 5) ExplainedConcept: concept predicate failed
texplain.nim(168, 10) Hint: Non-matching candidates for e(10)
Expand All @@ -29,14 +23,8 @@ proc e(o: ExplainedConcept): int
required type for o: ExplainedConcept
but expression '10' is of type: int literal(10)
texplain.nim(128, 6) ExplainedConcept: undeclared field: 'foo'
texplain.nim(128, 6) ExplainedConcept: undeclared field: '.'
texplain.nim(128, 6) ExplainedConcept: expression '.' cannot be called
texplain.nim(128, 6) ExplainedConcept: expression '' has no type (or is ambiguous)
texplain.nim(128, 5) ExplainedConcept: concept predicate failed
texplain.nim(129, 6) ExplainedConcept: undeclared field: 'bar'
texplain.nim(129, 6) ExplainedConcept: undeclared field: '.'
texplain.nim(129, 6) ExplainedConcept: expression '.' cannot be called
texplain.nim(129, 6) ExplainedConcept: expression '' has no type (or is ambiguous)
texplain.nim(128, 5) ExplainedConcept: concept predicate failed
texplain.nim(172, 20) Error: type mismatch: got <NonMatchingType>
Expand Down Expand Up @@ -88,14 +76,8 @@ proc f(o: NestedConcept)
required type for o: NestedConcept
but expression 'y' is of type: MatchingType
texplain.nim(132, 6) RegularConcept: undeclared field: 'foo'
texplain.nim(132, 6) RegularConcept: undeclared field: '.'
texplain.nim(132, 6) RegularConcept: expression '.' cannot be called
texplain.nim(132, 6) RegularConcept: expression '' has no type (or is ambiguous)
texplain.nim(132, 5) RegularConcept: concept predicate failed
texplain.nim(133, 6) RegularConcept: undeclared field: 'bar'
texplain.nim(133, 6) RegularConcept: undeclared field: '.'
texplain.nim(133, 6) RegularConcept: expression '.' cannot be called
texplain.nim(133, 6) RegularConcept: expression '' has no type (or is ambiguous)
texplain.nim(132, 5) RegularConcept: concept predicate failed
texplain.nim(136, 5) NestedConcept: concept predicate failed
Expand All @@ -121,7 +103,25 @@ expression: f(y)'''



# line 120 HERE


















# line 124 HERE

type
ExplainedConcept {.explain.} = concept o
Expand Down
76 changes: 76 additions & 0 deletions tests/specialops/terrmsgs.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
discard """
action: reject
cmd: '''nim check $options $file'''
matrix: "; -d:testWithout"
"""

when not defined(testWithout): # test for same errors before and after
{.experimental: "dotOperators".}
{.experimental: "callOperator".}

# issue #13063

block:
type Foo = object
type Bar = object
x1: int
var b: Bar
block:
template `.`(a: Foo, b: untyped): untyped = 123
echo b.x #[tt.Error
^ undeclared field: 'x' for type terrmsgs.Bar [type declared in terrmsgs.nim(15, 8)]]#
block:
template `.()`(a: Foo, b: untyped): untyped = 123
echo b.x() #[tt.Error
^ attempting to call undeclared routine: 'x']#
block:
template `.=`(a: Foo, b: untyped, c: untyped) = b = c
b.x = 123 #[tt.Error
^ undeclared field: 'x=' for type terrmsgs.Bar [type declared in terrmsgs.nim(15, 8)]]#
# yeah it says x= but does it matter in practice
block:
template `()`(a: Foo, b: untyped, c: untyped) = echo "something"

# completely undeclared::
xyz(123) #[tt.Error
^ undeclared identifier: 'xyz']#

# already declared routine:
min(123) #[tt.Error
^ type mismatch: got <int literal(123)>]#

# non-routine type shows `()` overloads:
b(123) #[tt.Error
^ attempting to call routine: 'b']#

echo b.x #[tt.Error
^ undeclared field: 'x' for type terrmsgs.Bar [type declared in terrmsgs.nim(15, 8)]]#
echo b.x() #[tt.Error
^ attempting to call undeclared routine: 'x']#

# issue #7777

import macros

block:
type TestType = object
private_field: string

when false:
template getField(obj, field: untyped): untyped = obj.field

macro `.`(obj: TestType, field: untyped): untyped =
let private = newIdentNode("private_" & $field)
result = quote do:
`obj`.getField(`private`) #[tt.Error
^ attempting to call undeclared routine: 'getField']#

var tt: TestType
discard tt.field

block: # related to issue #6981
proc `()`(a:string, b:string):string = a & b
proc mewSeq[T](a,b:int)=discard
proc mewSeq[T](c:int)= discard
mewSeq[int]() #[tt.Error
^ type mismatch: got <>]#
22 changes: 22 additions & 0 deletions tests/specialops/tnewseq.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# issue #6981

import std/assertions

{.experimental: "callOperator".}

block: # issue #6981
proc `()`(a:string, b:string):string = a & b

var s = newSeq[int](3)

doAssert s == @[0, 0, 0]

block: # generalized example from #6981
proc mewSeq[T](a: int)=discard
proc mewSeq[T]()= discard
mewSeq[int]()

block: # issue #9831
type Foo = object
proc `()`(foo: Foo) = discard
let x = newSeq[int]()

0 comments on commit 555c5ed

Please sign in to comment.