From 0683e8f747b3f5605333cbb25884bc90ebaa07dc Mon Sep 17 00:00:00 2001 From: metagn Date: Mon, 28 Nov 2022 23:33:02 +0300 Subject: [PATCH] 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 (cherry picked from commit 555c5ed1a73fa783ee5b7526cb7a3f773f3b7f33) --- compiler/semcall.nim | 55 +++++++++++------- compiler/semexprs.nim | 9 ++- nimsuggest/tests/tgeneric_highlight.nim | 2 - tests/concepts/texplain.nim | 38 ++++++------- tests/specialops/terrmsgs.nim | 76 +++++++++++++++++++++++++ tests/specialops/tnewseq.nim | 22 +++++++ 6 files changed, 156 insertions(+), 46 deletions(-) create mode 100644 tests/specialops/terrmsgs.nim create mode 100644 tests/specialops/tnewseq.nim diff --git a/compiler/semcall.nim b/compiler/semcall.nim index a3064788eb..5715e46d09 100644 --- a/compiler/semcall.nim +++ b/compiler/semcall.nim @@ -288,8 +288,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) @@ -331,7 +346,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: @@ -364,11 +379,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: @@ -400,7 +419,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 ".()" @@ -414,23 +433,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() = - # 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: @@ -654,7 +665,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.. @@ -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 @@ -121,7 +103,25 @@ expression: f(y)''' -# line 120 HERE + + + + + + + + + + + + + + + + + + +# line 124 HERE type ExplainedConcept {.explain.} = concept o diff --git a/tests/specialops/terrmsgs.nim b/tests/specialops/terrmsgs.nim new file mode 100644 index 0000000000..d1a790e540 --- /dev/null +++ b/tests/specialops/terrmsgs.nim @@ -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 ]# + + # 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 <>]# diff --git a/tests/specialops/tnewseq.nim b/tests/specialops/tnewseq.nim new file mode 100644 index 0000000000..970a5a8c29 --- /dev/null +++ b/tests/specialops/tnewseq.nim @@ -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]()