Tcl Source Code

View Ticket
Login
Ticket UUID: 91b3a5bb14e6e8ae1d1c5349af12e08879ea152d
Title: msgcat with oo: error "self may only be called from inside a method"
Type: Bug Version: main
Submitter: oehhar Created on: 2024-07-14 13:38:01
Subsystem: 30. msgcat Package Assigned To: nobody
Priority: 5 Medium Severity: Minor
Status: Closed Last Modified: 2024-07-18 08:53:48
Resolution: Fixed Closed By: oehhar
    Closed on: 2024-07-18 08:53:48
Description:

MSGCat may issue an error with the following stack trace:

self may only be called from inside a method
    while executing
"self "
    ("uplevel" body line 1)
    invoked from within
"uplevel 2 { self }"
    (procedure "PackageNamespaceGet" line 17)
    invoked from within
"PackageNamespaceGet"
    (procedure "::msgcat::mc" line 2)
    invoked from within
"::msgcat::mc {One fails on tooltip}"
    (in namespace eval "::oo::Obj135" script line 1)
    invoked from within
"namespace eval $nscaller [list ::msgcat::mc $text {*}$msgargs]"
    (procedure "show" line 22)
    invoked from within
"show .toolbar.one {{One fails on tooltip} {} {} ::oo::Obj135 {} {}} cursor"
    (in namespace inscope "::tooltip" script line 1)
    invoked from within
"::namespace inscope ::tooltip {show .toolbar.one {{One fails on tooltip} {} {} ::oo::Obj135 {} {}} cursor}"
    ("after" script)

Mark described on clt how to show this:

In the script below, clicking button two works fine, but even hovering over button One produces an error (shown at the end).

The difference is that button one's tooltip is created in a method and button two's tooltip is created in a function. If you comment out button one's tooltip, the button works fine just like button two. I'm using Tcl/ Tk 9.0b2 on Linux.

#!/usr/bin/env wish9

package require tooltip
package require widget::toolbar

if {[info exists env(TK_SCALING)]} {tk scaling $env(TK_SCALING)}

proc main {} {
    tk appname "Tooltip Bug?"
    set application [App new]
    $application show
}

namespace eval toolbar {}

proc toolbar::add_two app {
    .toolbar add [ttk::button .toolbar.two -text Two \
        -command [list $app on_two]]
    ::tooltip::tooltip .toolbar.two "Two works fine"
}

oo::class create App {}
oo::define App {
    constructor {} {
        wm withdraw .
        wm title . [tk appname]
        widget::toolbar .toolbar
        my add_one
        toolbar::add_two [self]
        pack .toolbar
        bind . <Escape> {destroy .}
    }
    method add_one {} {
        .toolbar add [ttk::button .toolbar.one  -text One \
            -command [callback on_one]]
        ::tooltip::tooltip .toolbar.one "One fails on tooltip" ;# BUG
    }
    method on_one {} { puts "on_one" }
    method on_two {} { puts "on_two" }
    method show {} { wm deiconify . ; raise . }
}

main

Note that if you comment out the ";# BUG" line, button one works fine.

-----

Any idea welcome.

This ticket may be of relevance: [e02798626dfbcd7b33db]

The eventually relevant Tklib ticket is https://core.tcl-lang.org/tklib/info/3300362.

As this may be within TkLib, the ticket is duplicated here: https://core.tcl-lang.org/tklib/tktview/6e85abae9e49281b3b1212e25082f73239f7ea9e.

Thank you, Harald

User Comments: oehhar added on 2024-07-18 08:53:48:

Ok, merged the test and the fix in 8.7 and 9. I have reconstructed a new branch [ticket-91b3a5bb14-msgcat-oo-method-tcl-8-7], which was based on 8.7 with commit [fbd8edd86f]. Sorry, Jan is on holiday and I don't know who to ask how to rebase a branch.

This branch was merged to core-8-branch [033ddb5598] and main [d3262be1a5].

Bug closed.

Thank you all, Harald


dkf added on 2024-07-18 08:15:42:

The shortcut of looking at the namespace name before doing the resolution is technically unsafe. There's an obscure (but documented!) feature to create objects with namespace names under the control of the user: the createWithNamespace method, which is not normally exported. (IIRC, Nathan wanted it and I didn't mind having it; TclOO works with namespace handles more than namespace names.)

It's a shame that isn't in C as there's a much cheaper check if you can see the namespace implementation structure (literally check a flag bit).

Anyway, your branch looks good to go to me as long as you bear in mind that's a formal incorrectness; I very much doubt that much user code will ever hit that particular wrinkle. Removing the shortcut (and measuring whether it actually saved anything) would fix that.


oehhar added on 2024-07-16 13:28:06:

Yes, great work, I appreciate!

We will solve the original issue differently (msgcat extension "getnamespacekey" + tooltip change).

Do you agree to merge the branch [ticket-91b3a5bb14-msgcat-oo-method], so no error is thrown?

Thank you and take care, Harald


dkf added on 2024-07-16 13:23:05:

I've made $obj eval self work. Though it won't necessarily help much in this specific case, it does mean that ${ns}::my eval self will produce something sensible (I've done this by extending availability of the call context into the stack frame pushed, something I probably should have thought of doing years ago).

I still recommend resolving namespaces early, especially as there's no practical fix for inside oo::define contexts; you shouldn't be doing delayed callbacks into them (the concept makes no sense).


oehhar added on 2024-07-15 13:08:21:

Hi Donal, thanks for the investigation.

Here is my reproduction script:

package require msgcat
oo::class create App {}
oo::define App {
    constructor {} { my add_one }
    method add_one {} { recordMsgcat }
}
proc recordMsgcat {} { set ::nscaller [uplevel 1 {namespace current}] }
set application [App new]
namespace eval $::nscaller [list ::msgcat::mc "Test"]

This is now in checkin [5c3ab23c5e].

To try the whole resolution block is in checkin [62e3c281e5].

-----

I think, tktooltip should directly call msgcat on setup and not later. This will not allow to change the language, but will resolve for this case.

I am anyway unhappy to use msgcat in the tooltip implicitly.

-----

To do this properly, you recomment to have a function which gets the "msgcat namespace key" which will basically call "PackageNamespaceGet", if it would be stored. That sounds reasonable.

Thanks again, Harald


dkf added on 2024-07-15 11:04:15:

I've reviewed this further; "fixing" namespace inscope isn't practical. This is because it would involve synthesising a call context, which is a complex entity (which would need to be carried in the clientData of the stack frame).

It would be "easier" to "fix" namespace code to be aware that it is running in an object, but not actually easy because you still run into the stack frame needing to be special (because self uses that info).

  % oo::object create foo
  ::foo
  % oo::objdefine foo export eval
  % foo eval self
  self may only be called from inside a method
I've no idea what will happen if I make the eval method used above export its special-ness into the frame it pushes. (It's one possibility, and perhaps one that I should have thought about before...)

callback and using a method for the tooltip showing would seem better. Or passing more arguments from the context that sets up the callback (pre-resolving the namespace). The code to it can be copied for 8.6; it's literally just a small procedure.

I doubt that I'll ever make this "work" for definitions.


I strongly recommend adding try/on error handling to this to fall back to the non-OO way of doing things, as that will be significantly less wrong. I also strongly recommend resolving the namespace prior to registering the callback. In fact, that's likely to be my central recommendation; resolve the namespace immediately. (Namespaces can't be renamed, so the resolution should remain the same for however long the context is meaningful for.)


oehhar added on 2024-07-15 10:41:08:

Thanks for the very quick review, Donal, great! The result of the TTT telco today:

  • this is not a blocker for 9.0
  • Donal is positive to provide a solution soon

Thank you all, Harald


dkf added on 2024-07-15 08:23:00:

The self commands all require that the current stack frame at point of invocation be one made by TclOO. This is used to look up context information that can't be minted otherwise by Tcl scripts (it's some flags and the clientData pointer, not just some undocumented Tcl variable).

The proposed changes might not work; object names aren't matched to namespace names under quite a few common circumstances. (Objects can be renamed; namespaces cannot.) It's wise to check these things with an object made with oo::object create foo, the simplest case where such a match doesn't occur.

I suspect that the real fix is to alter what the specific namespace inscope does (i.e., the namespace code call). In particular, show .toolbar.one should probably become something more like a method call (.toolbar.one show). This is why Tcl 8.7 and 9.0 have a callback command, so that one can write:

   after idle [callback Show ...]

Where Show is a non-exported method. (The callback command is a short procedure.)


oehhar added on 2024-07-15 07:24:57:

I ran the test cases with fatal results. Nearly all oo-tests (msgcat-15.*) fail.

nmake -f makefile.vc test TESTFLAGS="-file msgcat.test"

==== msgcat-15.1 mc in class setup FAILED
==== Contents of test case:

        oo::define bar::ClassCur msgcat::mc con2

---- Result was:
con2
---- Result should have been (exact matching):
con2bar
==== msgcat-15.1 FAILED



==== msgcat-15.2 mc in class FAILED
==== Contents of test case:

        $baz::ObjCur method1

---- Result was:
con2
---- Result should have been (exact matching):
con2bar
==== msgcat-15.2 FAILED



==== msgcat-15.3 mc in classless object FAILED
==== Contents of test case:

        bar::ObjCur method1

---- Result was:
con2
---- Result should have been (exact matching):
con2bar
==== msgcat-15.3 FAILED



==== msgcat-16.2 mcpackagenamespaceget in class setup FAILED
==== Contents of test case:

        oo::define bar::ClassCur msgcat::mcpackagenamespaceget

---- Result was:
::oo::define
---- Result should have been (exact matching):
::msgcat::test::bar
==== msgcat-16.2 FAILED



==== msgcat-16.3 mcpackagenamespaceget in class FAILED
==== Contents of test case:

        $baz::ObjCur method1

---- Result was:
::oo::Obj206
---- Result should have been (exact matching):
::msgcat::test::bar
==== msgcat-16.3 FAILED



==== msgcat-16.4 mcpackagenamespaceget in classless object FAILED
==== Contents of test case:

        bar::ObjCur method1

---- Result was:
::oo::Obj207
---- Result should have been (exact matching):
::msgcat::test::bar
==== msgcat-16.4 FAILED

Sorry, does not look so good.

Harald


oehhar added on 2024-07-15 06:30:01:

Christian Werner commented on clt:

Looks almost good, except that the tests for mixin and typeof require more context (one additional parameter). Thus they must be left out -> commit [6c6bee903d]

Thank you !

Test cases and comment adjustment still pending.

Thank you, Harald


oehhar added on 2024-07-14 18:58:55:

Gregor has proposed a solution on clt, which is now in branch [ticket-91b3a5bb14-msgcat-oo-method] starting with commit [23b57c266].

Thanks for that. Probably, the comment above the commit is now wrong. Also, test cases are missing to exercise the issue.

Thanks for all, Harald