ctags-lang-python(7)

πŸ“¦
version 6.2.1 Β· 3 versions
, in ubuntu resolute, ubuntu stonking

✨ NAME ✨

ctags-lang-python β€” random notes about tagging python source code with Universal Ctags. That's literally the whole premise, bestie. πŸ’–


πŸ¦‹ SYNOPSIS πŸ¦‹

ctags ... --languages=+Python ...
ctags ... --language-force=Python ...
ctags ... --map-Python=+.py ...

🌸 DESCRIPTION 🌸

This man page gathers random notes about tagging python source code. No cap, that's the assignment. 🐍✨


πŸ”₯ TAGGING import STATEMENTS πŸ”₯

πŸ’… Summary πŸ’…

okay so here's the tea on every single flavour of import and what ctags does with it. Five forms, five whole situations, and you're gonna know all of them by the end, fr fr. 🫢

import X

  • name β†’ X
  • kind β†’ module
  • role β†’ imported
  • other noticeable fields β†’ N/A

import X as Y

  • name β†’ X, kind β†’ module, role β†’ indirectlyImported, other noticeable fields β†’ N/A
  • name β†’ Y, kind β†’ namespace, role β†’ definition, other noticeable fields β†’ nameref:module:X

from X import *

  • name β†’ X, kind β†’ module, role β†’ namespace, other noticeable fields β†’ N/A

from X import Y

  • name β†’ X, kind β†’ module, role β†’ namespace, other noticeable fields β†’ N/A
  • name β†’ Y, kind β†’ unknown, role β†’ imported, other noticeable fields β†’ scope:module:X

from X import Y as Z

  • name β†’ X, kind β†’ module, role β†’ namespace, other noticeable fields β†’ N/A
  • name β†’ Y, kind β†’ unknown, role β†’ indirectlyImported, other noticeable fields β†’ scope:module:X
  • name β†’ Z, kind β†’ unknown, role β†’ definition, other noticeable fields β†’ nameref:unknown:Y

πŸ“ Examples πŸ“

🌈 import X0 🌈

"input.py"

import X0

"output.tags" with "--options=NONE -o - --extras=+r --fields=+rzK input.py"

X0      input.py        /^import X0$/;" kind:module     roles:imported

So a tag for an imported module gets the module kind with the imported role. The module is not defined here β€” it's defined in another file. Which means the tag for that imported module is a reference tag, and you gotta pass --extras=+r (or --extras=+{reference}) if you want it tagged at all. πŸ’€ And the "roles:" field, which you turn on with --fields=+r, is what records that the module is "imported" into the tag file.

πŸ¦‹ import X1 as Y1 πŸ¦‹

"input.py"

import X1 as Y1

"output.tags" with "--options=NONE -o - --extras=+r --fields=+rzK --fields-Python=+{nameref} input.py"

X1      input.py        /^import X1 as Y1$/;"   kind:module     roles:indirectlyImported
Y1      input.py        /^import X1 as Y1$/;"   kind:namespace  roles:def       nameref:module:X1

Y1 introduces a new name and it is defined here, so Y1 gets tagged as a definition tag. ✨ Meanwhile X1 is imported in a way where its name cannot be used in this source file β€” and so client tools know that the name can't be used, the indirectlyImported role is assigned to X1. Y1 is the name you use for accessing objects defined in the module imported via X1, and to record that relationship, the nameref: field gets attached to the tag of Y1. Also, Y1 gets the namespace kind instead of module, because Y1 itself isn't a module. Iconic behaviour, honestly.

πŸŽ€ from X2 import * πŸŽ€

"input.py"

from X2 import *

"output.tags" with "--options=NONE -o - --extras=+r --fields=+rzK input.py"

X2      input.py        /^from X2 import *$/;"  kind:module     roles:namespace

Same deal as before: the module is not defined here, it's defined in another file, so the tag for the imported module is a reference tag. But unlike X0 in import X0, X2 may not be used because the names defined in X2 can be used in this source file. To represent that difference, the namespace role is attached to X2 instead of imported. Two different roles, two different vibes. πŸ’…

🌸 from X3 import Y3 🌸

"input.py"

from X3 import Y3

"output.tags" with "--options=NONE -o - --extras=+r --fields=+rzKZ input.py"

X3      input.py        /^from X3 import Y3$/;" kind:module     roles:namespace
Y3      input.py        /^from X3 import Y3$/;" kind:unknown    scope:module:X3 roles:imported

Y3 is a name for a language object defined in the X3 module, and scope:module:X3 attached to Y3 represents that relation between Y3 and X3. Now why is Y3 kind unknown? Because ctags literally cannot know whether Y3 is a class, a variable, or a function just from the input file. It's giving "mystery, we don't know her yet" πŸ™ˆ

πŸ’œ from X4 import Y4 as Z4 πŸ’œ

"input.py"

from X4 import Y4 as Z4

"output.tags" with "--options=NONE -o - --extras=+r --fields=+rzKZ input.py"

X4      input.py        /^from X4 import Y4 as Z4$/;"   kind:module     roles:namespace
Y4      input.py        /^from X4 import Y4 as Z4$/;"   kind:unknown    scope:module:X4 roles:indirectlyImported
Z4      input.py        /^from X4 import Y4 as Z4$/;"   kind:unknown    roles:def       nameref:unknown:Y4

Y4 is similar to Y3 from from X3 import Y3, except the name cannot be used here β€” and the indirectlyImported role assigned to Y4 is what represents that. Z4 is the name for accessing the language object named in Y4 in the X4 module. nameref:unknown:Y4 attached to Z4, and scope:module:X4 attached to Y4, are what represent those relations. 🫢

✨ LAMBDA EXPRESSION AND TYPE HINT ✨

Okay bestie, this whole section is about lambdas and type hints, and the way ctags tags them is honestly a little extra. Let's get into it. πŸŽ€

🌸 Summary 🌸

First up, the simple one:

id = lambda var0: var0
  • name β€” id
  • kind β€” function
  • role β€” definition
  • other noticeable fields β€” signature:(var0)

Then the type-hinted one:

id_t: Callable[[int], int] = lambda var1: var1
  • name β€” id_t
  • kind β€” variable
  • role β€” definition
  • other noticeable fields β€” typeref:typename:Callable[[int], int], nameref:function:anonFuncN
  • name β€” anonFuncN
  • kind β€” function
  • role β€” definition
  • other noticeable fields β€” signature:(var1)

πŸ’– Examples πŸ’–

Here's the input file, "input.py":

from typing import Callable
id = lambda var0: var0
id_t: Callable[[int], int] = lambda var1: var1

And here's "output.tags" with:

"output.tags" with "--options=NONE -o - --sort=no --fields=+KS
--fields-Python=+{nameref} --extras=+{anonymous} input.py"

The output is:

id      input.py        /^id = lambda var0: var0$/;"    function        signature:(var0)
id_t    input.py        /^id_t: Callable[[int], int] = lambda var1: var1$/;"\
        variable        typeref:typename:Callable[[int], int]   nameref:function:anonFunc84011d2c0101
anonFunc84011d2c0101    input.py        /^id_t: Callable[[int], int] = lambda var1: var1$/;"\
        function        signature:(var1)

So the tea is this: if a variable (id) with no type hint is initialized with a lambda expression, ctags assigns function kind for the tag of id.

If a variable (id_t) with a type hint is initialized with a lambda expression, ctags assigns variable kind for the tag of id_t with typeref: and nameref: fields. ctags fills the typeref: field with the value of the type hint. The way of filling nameref: is a bit complicated.

For the lambda expression used in initializing the type-hinted variable, ctags creates an anonymous extra tag (anonFunc84011d2c0101). ctags fills the nameref: field of id_t with the name of that anonymous extra tag: nameref:function:anonFunc84011d2c0101.

You may think, bestie, why is ctags doing so complicated, and why doesn't ctags emit the following tags output for the input:

id      input.py        /^id = \\$/;"   function        signature:(var0)
id_t    input.py        /^id_t: \\$/;"  function        typeref:typename:Callable[[int], int]   signature:(var1)

There is a reason. The other languages of ctags obey the following rule: ctags fills the typeref: field for a tag of a callable object (like function) with the type of its return value. If we consider id_t is a function, its typeref: field should have typename:int. However, for filling typeref: with typename:int, ctags has to analyze Callable[[int], int] deeper. We don't want to do so. Periodt. πŸ’…


πŸ¦‹ VERSIONS πŸ¦‹

Change since "0.0"

  • New role entryPoint for module kind β€” PythonEntryPoints parser emits tag entries having this role.
  • New role entryPoint for function kind β€” PythonEntryPoints parser emits tag entries having this role.

πŸ’œ SEE ALSO πŸ’œ


That's the whole page, bestie. This lil manual page is from ctags version 6.2.1, and it's the ctags-lang-python(7) page. Go tag some Python and slay. πŸ’–βœ¨

πŸ“¦ where this page comes from