[vim/vim] Vim9script - Bug in the autoload mechanism? (Issue #10186)

133 views
Skip to first unread message

Enrico Maria De Angelis

unread,
Apr 13, 2022, 1:17:04 PM4/13/22
to vim/vim, Subscribed

Steps to reproduce

(I originally posted this question on vi.stackexchange, and I'm posting it again here upon D. Ben Knoble's suggestion.)

Put this content in ~/.vim/plugin/messingaround.vim

vim9script noclear

import autoload 'messingaround.vim' as xyz

if exists("g:loaded_messingaround")
  finish
endif
g:loaded_messingaround = 1
var save_cpo = &cpo
set cpo&vim

if !hasmapto('<Plug>MessingAround;')
  map <unique> <Leader>a  <Plug>MessingAround;
endif
noremap <unique> <script> <Plug>MessingAround; :echo <ScriptCmd>xyz.Say(expand("<cword>"))<CR>

&cpo = save_cpo

and this content in ~/.vim/autoload/messingaround.vim

vim9script noclear

export def Say(str: string): string
  return str
enddef

Temporarly empty your ~/.vimrc (I couldn't reproduce the problem with -u NONE, but I haven't looked much into it) and open a new Vim session, type a word into it, and hit Leadera.

You'll see the command line being populated with :echo (trailing space included), but no trace of Say being run.

Expected behaviour

The word under the cursor should be echoed.

Notice that if I don't use the autoload mechanism, i.e. if I

  • define Say in the ~/.vim/plugin/messingaround.vim file (and delete the file in autoload),
  • remove the keyword export
  • change <ScriptCmd>xyz. to <SID>

the function behaves as I expect.

Not sure if in the original case I expect the wrong thing, though.

Version of Vim

8.2.4651

Environment

Operating system: Archlinux (up-to-date)
Terminal: URxvt
Value of $TERM: rxvt-unicode-256color
Shell: GNU bash 5.1.16(1)

Logs and stack traces

No response


Reply to this email directly, view it on GitHub.
You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/issues/10186@github.com>

lacygoill

unread,
Apr 13, 2022, 1:41:54 PM4/13/22
to vim/vim, Subscribed

You'll see the command line being populated with :echo (trailing space included), but no trace of Say being run.

:echo expects an expression as argument, but <ScriptCmd>...<CR> is not an expression. <ScriptCmd>...<CR> just asks Vim to execute an arbitrary Ex command regardless of the current mode.

In contrast, <SID>Say(...) is an expression, so it works.


I suspect that you want to refer to an autoload function in a mapping using the script.func syntax, but you can't. There are still indeed a few contexts where it's not possible:

Not sure if we need yet another pseudo-key, some other mechanism, or just more documentation on this pitfall.
In any case, if you need to refer to an autoload function in a context where script.func is not valid, you can still use the legacy name script#func.


Unrelated, but you don't need to save and restore 'cpoptions'; it's done automatically:

A side effect of :vim9script is that the 'cpoptions' option is set to the
Vim default value, like with: >
:set cpo&vim
One of the effects is that |line-continuation| is always enabled.
The original value of 'cpoptions' is restored at the end of the script, while
flags added or removed in the script are also added to or removed from the
original value to get the same effect. The order of flags may change.
In the |vimrc| file sourced on startup this does not happen.

See :help vim9-namespace.


Reply to this email directly, view it on GitHub.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/issues/10186/1098317625@github.com>

lacygoill

unread,
Apr 13, 2022, 1:45:54 PM4/13/22
to vim/vim, Subscribed

In any case, if you need to refer to an autoload function in a context where script.func is not valid, you can still use the legacy name script#func.

Now that I think about it, that will be an issue when we start to refactor import autoload into import lazy. Then, there will no longer be any legacy name to fall back on. So, maybe we do need some new syntax/mechanism; I don't know.


Reply to this email directly, view it on GitHub.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/issues/10186/1098320897@github.com>

Enrico Maria De Angelis

unread,
Apr 13, 2022, 1:50:22 PM4/13/22
to vim/vim, Subscribed

In any case, if you need to refer to an autoload function in a context where script.func is not valid, you can still use the legacy name script#func.

Now that I think about it, that will be an issue when we start to refactor import autoload into import lazy. Then, there will no longer be any legacy name to fall back on. So, maybe we do need some new syntax/mechanism; I don't know.

Do you mean I shouldn't follow your original suggestion if I want my code to be compatible with wherever Vim9 will get to?


Reply to this email directly, view it on GitHub.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/issues/10186/1098324568@github.com>

lacygoill

unread,
Apr 13, 2022, 2:01:12 PM4/13/22
to vim/vim, Subscribed

Do you mean I shouldn't follow your original suggestion if I want my code to be compatible with wherever Vim9 will get to?

import lazy will not deprecate import autoload, so if you keep using the latter, then you will be able to use the legacy name script#func anywhere an autoload function name is accepted.

The issue will arise if you later decide to refactor import autoload into import lazy.


Reply to this email directly, view it on GitHub.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/issues/10186/1098333370@github.com>

Enrico Maria De Angelis

unread,
Apr 13, 2022, 3:35:28 PM4/13/22
to vim/vim, Subscribed

Regarding

:echo expects an expression as argument, but <ScriptCmd>...<CR> is not an expression. <ScriptCmd>...<CR> just asks Vim to execute an arbitrary Ex command regardless of the current mode.

In contrast, <SID>Say(...) is an expression, so it works.

and

I suspect that you want to refer to an autoload function in a mapping using the script.func syntax, but you can't. There are still indeed a few contexts where it's not possible:

would you mind quoting the bits of the documentation from which I'd have deduced the 3 phrases I've styled in bold above?


Reply to this email directly, view it on GitHub.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/issues/10186/1098410070@github.com>

lacygoill

unread,
Apr 13, 2022, 3:55:56 PM4/13/22
to vim/vim, Subscribed

... is not an expression

From :help <ScriptCmd>:

<ScriptCmd> is like <Cmd>

Then from :help <Cmd>:

The special text <Cmd> begins a "command mapping", it executes the command
directly without changing modes.

This states that a command is executed. Not that an expression is evaluated. At no point in the rest of this help tag is the word "expression" ever used.


<SID>Say(...) is an expression

<SID> is irrelevant here. What matters is Say() which is a function call. And a function call is an expression.

From :help expression-syntax or :help expr10:

function(expr1, ...)	function call

but you can't

To be clear: you can use script.func if and only if you're in a Vim9 context. The issue is that a mapping is run in the global context where only the legacy syntax is allowed. That's why <ScriptCmd> was introduced: to let you use the Vim9 syntax in a mapping. In practice, this covers most cases, but not all. For example, in your original mapping:

noremap <unique> <script> <Plug>MessingAround; :echo <ScriptCmd>xyz.Say(expand("<cword>"))<CR>
                                                     ^---------^

I guess you originally wanted to write something like this:

noremap <unique> <script> <Plug>MessingAround; :echo <C-R>=xyz.Say(expand("<cword>"))<CR>
                                                     ^----^

Then found out that it didn't work. I don't think this limitation is currently documented, which is why I wrote:

Not sure if we need yet another pseudo-key, some other mechanism, or just more documentation on this pitfall.


Reply to this email directly, view it on GitHub.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/issues/10186/1098428486@github.com>

Bram Moolenaar

unread,
Apr 13, 2022, 4:29:18 PM4/13/22
to vim/vim, Subscribed

It was mentioned before that a limitation of a mapping is that it just replaces typeahead by other text.
No script context can be used when the mapping expands. Thus the relation with the script must be included when
the mapping is defined. For that works fine.
Since you cannot use for a function like importName.Func() you need to split it up:

def LocalFunc(): string
  return xyz.Say(expand("<cword>"))
enddef
noremap <unique> <script> <Plug>MessingAround; :echo <SID>LocalFunc()<CR>

Disclaimer: I haven't tried it!

Perhaps it's possible to add something like for calling an imported function, but is it worth it?
Moving as much as possible into a compiled function is more useful.
Although, if the only thing that LocalFunc() would do is call the imported function, it does add some overhead.


Reply to this email directly, view it on GitHub.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/issues/10186/1098458344@github.com>

Bram Moolenaar

unread,
Apr 13, 2022, 4:48:01 PM4/13/22
to vim/vim, Subscribed

Hmm, perhaps we can make this work:

:echo <SID>xyz.Say(expand("<cword>"))<CR>

Need to recognize "{name}{dot}", replace with "{scriptnr}_" and when called load that script if needed, before calling the function.
Seems a bit like magic, but I think we have all the information needed.


Reply to this email directly, view it on GitHub.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/issues/10186/1098473828@github.com>

lacygoill

unread,
Apr 13, 2022, 4:57:51 PM4/13/22
to vim/vim, Subscribed

Perhaps it's possible to add something like for calling an imported function, but is it worth it?

I don't know, but at least we might want to improve the help with regards to how a user is expected to refer to a function in a mapping. In particular:

  • mappings are run in the global context where the legacy syntax is used
  • an exception is made for commands between <ScriptCmd> and <CR>, and for <expr> mappings, which are run in the script context; this lets you use the Vim9 syntax if your mapping is installed in a Vim9 script
  • if you need to refer to a function in a mapping using a Vim9 syntax, but the latter is not allowed, then use a wrapper function; for example:
vim9script
import 'script.vim'
def Wrapper(): string
  return script.func()
enddef
ioremap <key> <C-R>=<SID>Wrapper()<CR>

All of this might already be documented here and there, but maybe we should have 1 help tag somewhere at :help vim9 which centralizes all of this information.


Hmm, perhaps we can make this work:

Yes, I think this would fix most, if not all the remaining cases.


Seems a bit like magic, but I think we have all the information needed.

It's not really magic. From a mapping, we can refer to a script-local function via <SID>. Why couldn't we do the same for a variable? I already mentioned this pitfall here.

That's yet another pitfall which has always bothered me. We can access a script-local function from the global context, provided we know its ID (via the pseudo-key ), but we can't do the same for a script-local variable.


Reply to this email directly, view it on GitHub.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/issues/10186/1098480635@github.com>

lacygoill

unread,
Apr 13, 2022, 4:59:31 PM4/13/22
to vim/vim, Subscribed

It's not really magic. From a mapping, we can refer to a script-local function via . Why couldn't we do the same for a variable?

Ah, I guess script is not technically a variable. Still, it looks like one.


Reply to this email directly, view it on GitHub.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/issues/10186/1098481837@github.com>

Bram Moolenaar

unread,
Apr 14, 2022, 8:01:27 AM4/14/22
to vim/vim, Subscribed

With patch 8.2.4747 this should work.


Reply to this email directly, view it on GitHub.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/issues/10186/1099112496@github.com>

Bram Moolenaar

unread,
Apr 14, 2022, 8:04:09 AM4/14/22
to vim/vim, Subscribed

There actually is another solution, which was mentioned before and is used in a test: Use a funcref variable:

import './XsomeExport.vim' as some
var Funcy = some.Funcx
nnoremap <F3> :call <sid>Funcy(42)<cr>

But the straigtforward way of using <SID>some.Funcx() is much nicer.


Reply to this email directly, view it on GitHub.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/issues/10186/1099114465@github.com>

lacygoill

unread,
Apr 14, 2022, 9:36:33 AM4/14/22
to vim/vim, Subscribed

With patch 8.2.4747 this should work.

It works for a function defined under import/:

vim9script
var dir = '/tmp/.vim'
dir->delete('rf')
&runtimepath = dir
dir ..= '/import'
dir->mkdir('p')
var lines =<< trim END
    vim9script
    export def Func()
        echomsg 'from Func()'
    enddef
END
lines->writefile(dir .. '/script.vim')
import 'script.vim'
nnoremap <F3> <Cmd>call <SID>script.Func()<CR>
feedkeys("\<F3>")
from Func()

But not for a function defined under autoload/:

vim9script
var dir = '/tmp/.vim'
dir->delete('rf')
&runtimepath = dir
dir ..= '/autoload'
dir->mkdir('p')
var lines =<< trim END
    vim9script
    export def Func()
        echomsg 'from Func()'
    enddef
END
lines->writefile(dir .. '/script.vim')
import autoload 'script.vim'
nnoremap <F3> <Cmd>call <SID>script.Func()<CR>
feedkeys("\<F3>")
E117: Unknown function: <SNR>2_Func

Could it work?


Reply to this email directly, view it on GitHub.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/issues/10186/1099194222@github.com>

Bram Moolenaar

unread,
Apr 14, 2022, 4:05:41 PM4/14/22
to vim/vim, Subscribed

Oh yes, a function from an actual autoload script isn't defined with but with # in the name.


Reply to this email directly, view it on GitHub.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/issues/10186/1099578042@github.com>

Bram Moolenaar

unread,
Apr 14, 2022, 4:37:00 PM4/14/22
to vim/vim, Subscribed

Closed #10186 via 648dd88.


Reply to this email directly, view it on GitHub.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/issue/10186/issue_event/6437125792@github.com>

Enrico Maria De Angelis

unread,
Apr 15, 2022, 5:44:58 AM4/15/22
to vim/vim, Subscribed

To be clear: you can use script.func if and only if you're in a Vim9 context. The issue is that a mapping is run in the global context where only the legacy syntax is allowed. That's why <ScriptCmd> was introduced: to let you use the Vim9 syntax in a mapping. In practice, this covers most cases, but not all. For example, in your original mapping:

I think I have a deep misunderstanding here. Does the above mean that using :*map commands means using non-Vim9 syntax?


Reply to this email directly, view it on GitHub.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/issues/10186/1100002582@github.com>

lacygoill

unread,
Apr 15, 2022, 7:01:47 AM4/15/22
to vim/vim, Subscribed

Does the above mean that using :*map commands means using non-Vim9 syntax?

In a mapping, the Vim9 syntax is allowed if and only if you're in a Vim9 script, and the code is:

Here are 2 examples:

vim9script
nnoremap <F3> <ScriptCmd>echo (() => 'this is a Vim9 lambda')()<CR>
feedkeys("\<F3>")
this is a Vim9 lambda
vim9script
nnoremap <expr> <F3> (() => ':echomsg "this is a Vim9 lambda"<CR>')()
feedkeys("\<F3>")
this is a Vim9 lambda

But still, the above looks a bit like the mappings will always be legacy.

I think you expect a mapping to let you use the Vim9 syntax just because it was installed from a Vim9 script. That would be possible if mappings were running in the context of the script where they were installed (just like user-defined Ex commands and autocmds). But unfortunately, they do not.

Mappings run in the global context. This means that when the mapping is actually processed, Vim has no clue about its script (let alone whether it's a Vim9 script or a legacy script). That's why we need this weird <SID> pseudo-key. This tells Vim that when it installs a mapping, it should remember the script ID, so that later, when the mapping is finally used, Vim knows in which script it can find the function to call.

It was annoying/confusing to have to use the legacy syntax in a mapping installed in a Vim9 script, which is why I opened the issue #9499

In response, Vim improved the mappings in 2 ways:

  • the patch 8.2.4059 lets Vim remember the script context in the rhs of an <expr> mapping: this allows you to use the Vim9 syntax in the rhs of an <expr> mapping
  • the patch 8.2.4099 introduces the new pseudo-key <ScriptCmd> which again makes Vim remember the script context: this allows you to use the Vim9 syntax between <ScriptCmd> and the next <CR>

Besides, thanks to your report, Vim has improved in 2 other ways:

  • the patch 8.2.4748 lets you refer to an imported function via <SID>script.Func()
  • the patch 8.2.4751 lets you do the same for an autoload function

Maybe I just don't understand how legacy and vim9 script are meant to overlap,

Ideally, there would never be the need to use the legacy syntax in a Vim9 script. But there are still a few cases where it's required. It all boils down to this issue: where you are when you write your code, and where Vim is when it runs this code, are not necessarily the same places. If Vim is in the global context when it runs your code, then the Vim9 syntax will not work.

In my experience, those cases are rare though.


for how long, and when (if) the latter will be a standalone language that needn't be combined with legacy to allow everything legacy allowed.

Vim9 will be released in a month or 2. What you can see right now will not be much different from the release.

That doesn't mean that the syntax can not be improved further. It can. I have a few ideas, but not enough time.


Reply to this email directly, view it on GitHub.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/issues/10186/1100039694@github.com>

Bram Moolenaar

unread,
Oct 11, 2022, 3:54:11 AM10/11/22
to vim/vim, Subscribed


> > To be clear: you can use `script.func` if and only if you're in a

> > Vim9 context. The issue is that a mapping is run in the global
> > context where only the legacy syntax is allowed. That's why
> > `<ScriptCmd>` was introduced: to let you use the Vim9 syntax in a

> > mapping. In practice, this covers most cases, but not all. For
> > example, in your original mapping:
>
> I think I have a deep misunderstanding here. Does the above mean that

> using `:*map` commands means using non-Vim9 syntax?

map commands themselves have no syntax at all. Only replacement of "<>"
items and escaping rules apply. Mappings specify to change typed
characters into other characters, which will then be handled in whatever
context that happens.

So basically, if I type "this" then do like I have typed "that".

Expression mappings are different though, as lacygoill explained.


--
For society, it's probably a good thing that engineers value function over
appearance. For example, you wouldn't want engineers to build nuclear power
plants that only _look_ like they would keep all the radiation inside.
(Scott Adams - The Dilbert principle)

/// Bram Moolenaar -- ***@***.*** -- http://www.Moolenaar.net \\\
/// \\\
\\\ sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ ///
\\\ help me help AIDS victims -- http://ICCF-Holland.org ///


Reply to this email directly, view it on GitHub.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/issues/10186/1274248155@github.com>

ubaldot

unread,
Apr 14, 2023, 5:17:24 AM4/14/23
to vim/vim, Subscribed

Sorry for posting in a Closed issue, but I couldn't find the answer in the :h pages.
I read many interesting workaround (including a wrapper, a funcref, re-defining your function so that you just <ScriptCmd>:call xyz.Say(...), and Bram also proposed the actually nicer <SID>xyz.Say()).

My question is: as it is today (April 2023), which is the suggested method?
Does <SID>xyz.Myfunc() work in Vim9? If so, is there any plans to update the docs?


Reply to this email directly, view it on GitHub.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/issues/10186/1508206795@github.com>

Reply all
Reply to author
Forward
0 new messages