fix bugs with dot & call operators [backport] (#20931)
* 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
This commit is contained in:
parent
f31dc63169
commit
555c5ed1a7
6 changed files with 156 additions and 49 deletions
|
|
@ -287,8 +287,23 @@ proc notFoundError*(c: PContext, n: PNode, errors: CandidateErrors) =
|
||||||
# fail fast:
|
# fail fast:
|
||||||
globalError(c.config, n.info, "type mismatch")
|
globalError(c.config, n.info, "type mismatch")
|
||||||
return
|
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:
|
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
|
return
|
||||||
|
|
||||||
let (prefer, candidates) = presentFailedCandidates(c, n, errors)
|
let (prefer, candidates) = presentFailedCandidates(c, n, errors)
|
||||||
|
|
@ -330,7 +345,7 @@ proc getMsgDiagnostic(c: PContext, flags: TExprFlags, n, f: PNode): string =
|
||||||
sym = nextOverloadIter(o, c, f)
|
sym = nextOverloadIter(o, c, f)
|
||||||
|
|
||||||
let ident = considerQuotedIdent(c, f, n).s
|
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
|
let sym = n[1].typ.typSym
|
||||||
var typeHint = ""
|
var typeHint = ""
|
||||||
if sym == nil:
|
if sym == nil:
|
||||||
|
|
@ -363,11 +378,15 @@ proc resolveOverloads(c: PContext, n, orig: PNode,
|
||||||
else:
|
else:
|
||||||
initialBinding = nil
|
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,
|
pickBestCandidate(c, headSymbol, n, orig, initialBinding,
|
||||||
filter, result, alt, errors, efExplain in flags,
|
filter, result, alt, dummyErrors, efExplain in flags,
|
||||||
errorsEnabled, flags)
|
false, flags)
|
||||||
pickBest(f)
|
|
||||||
|
|
||||||
let overloadsState = result.state
|
let overloadsState = result.state
|
||||||
if overloadsState != csMatch:
|
if overloadsState != csMatch:
|
||||||
|
|
@ -383,7 +402,7 @@ proc resolveOverloads(c: PContext, n, orig: PNode,
|
||||||
let op = newIdentNode(getIdent(c.cache, x), n.info)
|
let op = newIdentNode(getIdent(c.cache, x), n.info)
|
||||||
n[0] = op
|
n[0] = op
|
||||||
orig[0] = op
|
orig[0] = op
|
||||||
pickBest(op)
|
pickSpecialOp(op)
|
||||||
|
|
||||||
if nfExplicitCall in n.flags:
|
if nfExplicitCall in n.flags:
|
||||||
tryOp ".()"
|
tryOp ".()"
|
||||||
|
|
@ -397,26 +416,15 @@ proc resolveOverloads(c: PContext, n, orig: PNode,
|
||||||
let callOp = newIdentNode(getIdent(c.cache, ".="), n.info)
|
let callOp = newIdentNode(getIdent(c.cache, ".="), n.info)
|
||||||
n.sons[0..1] = [callOp, n[1], calleeName]
|
n.sons[0..1] = [callOp, n[1], calleeName]
|
||||||
orig.sons[0..1] = [callOp, orig[1], calleeName]
|
orig.sons[0..1] = [callOp, orig[1], calleeName]
|
||||||
pickBest(callOp)
|
pickSpecialOp(callOp)
|
||||||
|
|
||||||
if overloadsState == csEmpty and result.state == csEmpty:
|
if overloadsState == csEmpty and result.state == csEmpty:
|
||||||
if efNoUndeclared notin flags: # for tests/pragmas/tcustom_pragma.nim
|
if efNoUndeclared notin flags: # for tests/pragmas/tcustom_pragma.nim
|
||||||
template impl() =
|
result.state = csNoMatch
|
||||||
result.state = csNoMatch
|
if efNoDiagnostics in flags:
|
||||||
if efNoDiagnostics in flags:
|
return
|
||||||
return
|
# xxx adapt/use errorUndeclaredIdentifierHint(c, n, f.ident)
|
||||||
# xxx adapt/use errorUndeclaredIdentifierHint(c, n, f.ident)
|
localError(c.config, n.info, getMsgDiagnostic(c, flags, n, f))
|
||||||
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()
|
|
||||||
return
|
return
|
||||||
elif result.state != csMatch:
|
elif result.state != csMatch:
|
||||||
if nfExprCall in n.flags:
|
if nfExprCall in n.flags:
|
||||||
|
|
@ -619,7 +627,7 @@ proc explicitGenericSym(c: PContext, n: PNode, s: PSym): PNode =
|
||||||
proc explicitGenericInstantiation(c: PContext, n: PNode, s: PSym): PNode =
|
proc explicitGenericInstantiation(c: PContext, n: PNode, s: PSym): PNode =
|
||||||
assert n.kind == nkBracketExpr
|
assert n.kind == nkBracketExpr
|
||||||
for i in 1..<n.len:
|
for i in 1..<n.len:
|
||||||
let e = semExpr(c, n[i])
|
let e = semExprWithType(c, n[i])
|
||||||
if e.typ == nil:
|
if e.typ == nil:
|
||||||
n[i].typ = errorType(c)
|
n[i].typ = errorType(c)
|
||||||
else:
|
else:
|
||||||
|
|
|
||||||
|
|
@ -535,7 +535,7 @@ proc overloadedCallOpr(c: PContext, n: PNode): PNode =
|
||||||
result = newNodeI(nkCall, n.info)
|
result = newNodeI(nkCall, n.info)
|
||||||
result.add newIdentNode(par, n.info)
|
result.add newIdentNode(par, n.info)
|
||||||
for i in 0..<n.len: result.add n[i]
|
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) =
|
proc changeType(c: PContext; n: PNode, newType: PType, check: bool) =
|
||||||
case n.kind
|
case n.kind
|
||||||
|
|
@ -993,6 +993,9 @@ proc semIndirectOp(c: PContext, n: PNode, flags: TExprFlags; expectedType: PType
|
||||||
if s != nil:
|
if s != nil:
|
||||||
setGenericParams(c, n[0])
|
setGenericParams(c, n[0])
|
||||||
return semDirectOp(c, n, flags, expectedType)
|
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
|
var t: PType = nil
|
||||||
if n[0].typ != nil:
|
if n[0].typ != nil:
|
||||||
|
|
@ -1042,10 +1045,10 @@ proc semIndirectOp(c: PContext, n: PNode, flags: TExprFlags; expectedType: PType
|
||||||
instGenericConvertersSons(c, result, m)
|
instGenericConvertersSons(c, result, m)
|
||||||
|
|
||||||
else:
|
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,
|
# 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:
|
# 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?
|
# XXX: hmm, what kind of symbols will end up here?
|
||||||
# do we really need to try the overload resolution?
|
# do we really need to try the overload resolution?
|
||||||
n[0] = prc
|
n[0] = prc
|
||||||
|
|
|
||||||
|
|
@ -8,12 +8,10 @@ $nimsuggest --tester $file
|
||||||
highlight;;skType;;1;;7;;3
|
highlight;;skType;;1;;7;;3
|
||||||
highlight;;skProc;;1;;0;;6
|
highlight;;skProc;;1;;0;;6
|
||||||
highlight;;skProc;;1;;0;;6
|
highlight;;skProc;;1;;0;;6
|
||||||
highlight;;skType;;1;;7;;3
|
|
||||||
highlight;;skProc;;1;;0;;6
|
highlight;;skProc;;1;;0;;6
|
||||||
highlight;;skType;;2;;14;;3
|
highlight;;skType;;2;;14;;3
|
||||||
highlight;;skProc;;2;;7;;6
|
highlight;;skProc;;2;;7;;6
|
||||||
highlight;;skProc;;2;;7;;6
|
highlight;;skProc;;2;;7;;6
|
||||||
highlight;;skType;;2;;14;;3
|
|
||||||
highlight;;skProc;;2;;7;;6
|
highlight;;skProc;;2;;7;;6
|
||||||
highlight;;skTemplate;;3;;0;;8
|
highlight;;skTemplate;;3;;0;;8
|
||||||
highlight;;skType;;3;;9;;3
|
highlight;;skType;;3;;9;;3
|
||||||
|
|
|
||||||
|
|
@ -13,14 +13,8 @@ proc e(o: ExplainedConcept): int
|
||||||
required type for o: ExplainedConcept
|
required type for o: ExplainedConcept
|
||||||
but expression '10' is of type: int literal(10)
|
but expression '10' is of type: int literal(10)
|
||||||
texplain.nim(128, 6) ExplainedConcept: undeclared field: 'foo'
|
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(128, 5) ExplainedConcept: concept predicate failed
|
||||||
texplain.nim(129, 6) ExplainedConcept: undeclared field: 'bar'
|
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(128, 5) ExplainedConcept: concept predicate failed
|
||||||
|
|
||||||
texplain.nim(168, 10) Hint: Non-matching candidates for e(10)
|
texplain.nim(168, 10) Hint: Non-matching candidates for e(10)
|
||||||
|
|
@ -29,14 +23,8 @@ proc e(o: ExplainedConcept): int
|
||||||
required type for o: ExplainedConcept
|
required type for o: ExplainedConcept
|
||||||
but expression '10' is of type: int literal(10)
|
but expression '10' is of type: int literal(10)
|
||||||
texplain.nim(128, 6) ExplainedConcept: undeclared field: 'foo'
|
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(128, 5) ExplainedConcept: concept predicate failed
|
||||||
texplain.nim(129, 6) ExplainedConcept: undeclared field: 'bar'
|
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(128, 5) ExplainedConcept: concept predicate failed
|
||||||
|
|
||||||
texplain.nim(172, 20) Error: type mismatch: got <NonMatchingType>
|
texplain.nim(172, 20) Error: type mismatch: got <NonMatchingType>
|
||||||
|
|
@ -88,14 +76,8 @@ proc f(o: NestedConcept)
|
||||||
required type for o: NestedConcept
|
required type for o: NestedConcept
|
||||||
but expression 'y' is of type: MatchingType
|
but expression 'y' is of type: MatchingType
|
||||||
texplain.nim(132, 6) RegularConcept: undeclared field: 'foo'
|
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(132, 5) RegularConcept: concept predicate failed
|
||||||
texplain.nim(133, 6) RegularConcept: undeclared field: 'bar'
|
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(132, 5) RegularConcept: concept predicate failed
|
||||||
texplain.nim(136, 5) NestedConcept: concept predicate failed
|
texplain.nim(136, 5) NestedConcept: concept predicate failed
|
||||||
|
|
||||||
|
|
@ -121,7 +103,25 @@ expression: f(y)'''
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# line 120 HERE
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# line 124 HERE
|
||||||
|
|
||||||
type
|
type
|
||||||
ExplainedConcept {.explain.} = concept o
|
ExplainedConcept {.explain.} = concept o
|
||||||
|
|
|
||||||
76
tests/specialops/terrmsgs.nim
Normal file
76
tests/specialops/terrmsgs.nim
Normal file
|
|
@ -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
tests/specialops/tnewseq.nim
Normal file
22
tests/specialops/tnewseq.nim
Normal file
|
|
@ -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]()
|
||||||
Loading…
Add table
Add a link
Reference in a new issue