Replies: 22 comments 22 replies
-
"Lets start with nothing in vlib uses v.mod files. Thats ok because formally v.mod files are used for packages in vpm." This is incorrect. If you look, there is a This is all by design, to keep V modular, and to make the modules under |
Beta Was this translation helpful? Give feedback.
-
Revised code is working, all test passing. Im adding new tests. ctk@EXWMBPROM301 nv % ./w vlib/v/compiler_errors_test.v
|
Beta Was this translation helpful? Give feedback.
-
Code was pushed to my repo: |
Beta Was this translation helpful? Give feedback.
-
This fixes this issue 24430 |
Beta Was this translation helpful? Give feedback.
-
Very comprehensive your text about modules. Something to study carefully. Even though I think few is documented or nothing at all about the fact (taken from Golang) that prevents importing cycles (not a problem in Java as far as I recall). So when the number of modules start to grow one need a good plan to prevent biting own's tail.
|
Beta Was this translation helpful? Give feedback.
-
Adding this here for ref. |
Beta Was this translation helpful? Give feedback.
-
There are several places, where you ask "Should we force vlib/?" . |
Beta Was this translation helpful? Give feedback.
-
No. Using |
Beta Was this translation helpful? Give feedback.
-
My opinion, as I mentioned in the comments in |
Beta Was this translation helpful? Give feedback.
-
If pref.path is a file, instead of a folder, then |
Beta Was this translation helpful? Give feedback.
-
Another test, that you can do is:
|
Beta Was this translation helpful? Give feedback.
-
If I remember correctly, yes, it was related to the / vs \ - the output of vfmt oscillated on windows. It may no longer be the case now. |
Beta Was this translation helpful? Give feedback.
-
I need further guidance about the modules folder If we say: Inside a project folder we can organize modules inside a folder named Then we don't make distinctions between a project and a module. A project is just the main module being build. Therefore a module can contain a modules/ folder. But then we could have these scenarios:
Do we have further guidance on the use of the |
Beta Was this translation helpful? Give feedback.
-
@spytheman, should I re-enable looking in the parent folder or fix the cases were I find them? I modified the logic so we will not resolve siblings, @spytheman and I agree that is not a proper use case of the "import" statement. I call resolving siblings when we have the following: ./mod1/
|-- mod1.v
./mod2/
|-- mod2.v The in
As a side project we could do some clean-up of our tests:
|
Beta Was this translation helpful? Give feedback.
-
@ctkjose
This is to give a perspective. As a user I would see any kind of special requirement as limiting my project, other than mandated by the need for a unique libray path/identifier for a package manager or some feature of the language, like I like the approach here, but can't add to the details. |
Beta Was this translation helpful? Give feedback.
-
@Wajinn I totally agree and I love how easy is to align V modules with your personal intuition to organize your code. The issue that started this fix is that sadly modules are broken on the user projects. The idea here is just to fine tune the current module implementation, check the first entry of this discussion where I trow some ideas of what a more formal definition of modules and basic rules the code could follow. Internally V has four different functions (or five if you include the one in vdoc) related to resolving: the qualified name of a module, the folder of a module and the v.mod file of a module. There are various issues (apart from inefficiencies) with these functions, the main ones being:
The current fix doesn't change how modules work, the folder structure is arbitrary with the exception that |
Beta Was this translation helpful? Give feedback.
-
It has no relation to module lookup bugs and the fully qualified module name mismatches. |
Beta Was this translation helpful? Give feedback.
-
I can not give you a general answer, without considering each case separately. ...
|
Beta Was this translation helpful? Give feedback.
-
imho that could be done separately, to avoid losing the focus of the discussion. |
Beta Was this translation helpful? Give feedback.
-
what will that expand to? |
Beta Was this translation helpful? Give feedback.
-
I think is appropriate to maybe reset the discussion and try to conceptually define modules in V. I leave this here as a starting point... Edits4/JUN/2025, Edited to include @spytheman comments Lets revisit modules in VThe main purpose of modules are to:
Conceptually a module is a logical aggregate of source code with a shared module name that becomes a namespace. A module is just a folder with All the
Not every folder needs to be a module. We can organized our modules intuitively using folders just to have a logical structure that is more natural and organic to the developer. For example we have modules to use Google's API, we have a module for the
A module folder can have sub modules which are simply folders that we use to further divide and organize the code/functionality provided by the module. In all of these operations we are very conscious of io operations, we limit ourselves to stats with Packages and v.modA Adding a If module is a VPM package, then the root folder (ie: No distinctions are made between a package and a module. Evaluate the need for an empty
|
Beta Was this translation helpful? Give feedback.
-
V is 6 years old, and the current module import system has been used the entire time it has existed. Yes, there are a few flaws, but does that mean we need to completely toss it out and start over? Or just fix the current problems and move on? |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
This is just a draft of a PR for fixing some standing issues with module resolution in V. Just trying to get some opinions before I make the PR.
Module handling and Source Code Organization
There are some grey areas and inconsistencies when it comes to the behavior of modules/packages and v.mod files. We still have many notes in the source code regarding this issue.
The vlib is an excellent source of examples of the intention and uses cases for modules in the language.
Lets start with nothing in vlib uses
v.mod
files. Thats ok because formallyv.mod
files are used for packages in vpm.What is a module
From the documentation a module is just a folder with
*.v
files. A module name as used with themodule
keyword is the name of the immediate folder, they must usesnake_case
and no qualified names like "a.b.c" are allowed.What is a package
For a package we state that it can contain one or more V modules and that a package should have a
v.mod
file at its top folder describing the contents of the package. In practicality a package is just a module with av.mod
file managed by VPM.Search Locations
Per documentation the option
-path
is the official means to set thepref.lookup_path[]
array. The full description of this option is:Specify the order of path V looks up in while searching for imported dependencies, separated by pipes (
|
). In addition to absolute paths, you can also use these special strings too:v install
are there). You can change its location by setting the environment variable VMODULES.-path "/v/my_project_private_modules|@vlib|@vmodules"
By default, -path is just "@vlib|@vmodules" .
In code
@vlib
ispref.vlib
and@vmodules
isos.vmodules_paths()
which in turn usesgetenv('VMODULES')
or<HOMDIR>/.vmodules
. As expected multiple paths are allowed in $VMODULES using:
on unix or;
on windows.The field
pref.path
has the path as entered by the user inrun
orcrun
. It could be '-' or empty, else was already checked withos.exists
.The
Builder.compiled_dir
is the projects root path being compiled initialized frompref.path
. In the parser everything is atomic, we only know the file's path and have a access to thepref
instance.The
Builder
usesmodule_search_paths
set byBuilder.set_module_lookup_paths()
. Here it starts withBuilder.compiled_dir
, then if found we add$compiled_dir/src/modules
and$compiled_dir/modules
. Finally it addspref.lookup_path[]
.In
Builder.parse_imports()
we useBuilder.find_module_path()
to resolve a Qualified Module Name (QMN) to a path. This one is more involved. Here the search order is as follows:<rel_to_file>
. It usesvmod
to find a module folder for<rel_to_file>
, if found it will search that folder.Builder.module_search_paths
to the search paths.os.getwd()
also.<rel_to_file>
contains amodules
segment, it will search that modules folder.The parser also has two function to compute a Qualified Module Name (QMN),
qualify_import()
andqualify_module()
that need to resolves names to paths. We will discuss those separate given the atomic nature/scope of the parser. But the gist is thatqualify_import()
searchespref.lookup_path
, then it also forcesos.vmodules_paths()
, lastly it will use<rel_to_dir>
. Inqualify_module()
we start checking relative toos.getwd()
, else it uses<rel_to_dir>
since we assume all files in a module are in a single folder.We have
./project/src
,./project/src/modules
and./project/modules
that are treated as special paths. Many VPM packages use this structure. These are presumed to be inspired by Go. Prior to Go 1.11 Go relied$GOPATH
env variable to define a workspace that had theGOPATH/src
andGOPATH/pkg
folders.To this point we can state that in V:
@vlib
and global modules.Qualified Module Name
The Qualified Module Name (QMN) is a more specific form (unambiguous) of qualifiers for a module name.
For a module in VLIB, the QMN is always is made up of all folder's name prior to
vlib/
. For files in.vmodules/
is similar but we stop if av.mod
file is found. In VPM we also stop at '.git', '.hg', '.svn' and '.v.mod.stop'.A QMN is computed by the functions
qualify_import()
andqualify_module()
. These functions are discussed in more detail later. These functions are used during the atomic parse process; they have no context, state, or reference to theBuilder
, particularly the project root (Builder.compiled_dir
). In lieu ofBuilder.compiled_dir
, we useos.getwd()
inqualify_module()
and assume that the working directory is the project root.Module Import
The QMN assigned to an import is computed at
parser.v:import_stmt()
usingqualify_import()
.qualify_import()
in turn usesmod_path_to_full_name()
. In qualify_import we searchpref.lookup_path
, then it also forcesos.vmodules_paths()
, lastly it will use<rel_to_dir>
.The
mod_path_to_full_name()
has a few quirks that we will look at later. Here we build a QMN by walking the folder matched inqualify_import()
(orqualify_module()
) backwards and stoping when the base name of one of the paths inpref.lookup_path
is found or when av.mod
file is found.In code we ALWAYS have to use the module alias. The module alias is the last segment of a QMN or the name set with
as
.In
parser.v:name_expr()
imported symbols get the QMN as their scope.Module Statement
Here the rules are simple. A module name is the folder name using snake_case, and no qualified names.
At
parser.module_decl()
we usequalify_module()
to establish the module's QMN. Here we have the advantage that we made the rule thatmodule a
must be inside a folder with the same name.giving the implications of the isolation of the parser these functions m
Issues
Invalid QMNs on import statements
In
qualify_import()
, the QMN is resolved relative to the file performing the import. This means that a QMN for a module outsidevlib/
and.vmodules/
may differ depending on where it is imported from. As a result, the same module can have different QMNs in different locations. When the QMN determined during import does not match the QMN resolved byqualify_module()
, it leads to unknown symbol errors.Invalid QMNs on module declarations
In
qualify_module()
we use theos.getwd()
which may be different thanBuilder.compiled_dir
. This means that the QMN for a module outsidevlib/
and.vmodules/
will be different QMN than the import.Ambiguities when resolving a modules path
When reading and parsing the imported files in
Builder.parse_imports()
, the files read used for the AST and builder's symbol table may NOT be the same as the one resolved by the parser inqualify_import()
andqualify_module()
.Using
os.getwd()
may lead to the wrong file being read at `Builder.parse_imports().Modules in
<compiled_dir>/modules
,<compiled_dir>/scr/modules
are never resolved inqualify_import()
. Imported symbols are scoped to the QMN the user wrote. It is up toBuilder.parse_imports()
to resolve the path. Inqualify_modules()
they are resolved simply because it found the base name "modules". If there is more than onemodules
segment in the path it would stop at the last one which also matches the behavior ofBuilder.find_module_path()
.In
qualify_modules()
it uses the base names (like "vlib", ".vmodules" and "modules") of key search paths instead of absolute paths this may leads to discrepancies in the QMN generated. For example the look_path '/my_modules/math' will addmath
as a stop folder when we need to resolveprj/modules/ai/math/vector
.Modules in
vlib/
and.vmodules/
are treated consistently in all the scenarios tested.VPM logic will only be used at
builder.parse_imports()
which will use.git
among other as a stop folder.We generally interpret the special folder
modules/
as<compiled_dir>/modules
but in reality is always<rel_to_dir>/modules
at the parser and thenbuilder.parse_imports()
will instead prioritize relative to<compiled_dir>
.Right now
mod_path_to_full_name()
ignoresv.mod
files and continues searching using the last one. The reason reads currently CI clones some modules into the v repo to test, the condition after'v.mod' in ls
can be removed once a proper solution is added. This is one of the reasons nested folder break in project space.Broken Nested modules on project side
Nested project modules loaded and parsed by
Builder.parse_imports()
nevertheless the discrepancy in QMN doesn't allow for symbols to be resolved. Playing with the placement of thev.mod
file on can get all the QMS to match and the compilation to succeed.This is really frustrating because you at-least expect for patterns that work on
vlib/
to work in the project space.Discussion of proposed solution
The solution follows the current status-quo of not requiring formal structures and code organization while supporting organic and intuitive code organization.
The key source of use cases for modules is our own vlib, it provides actual patterns that simple and work. Sadly thats not the case in the project-space.
We limit ourself to providing a predictable and consistent way to resolve a module location and its QMN initially in the builder and parser.
Revisiting the definition of modules in V
Conceptually a module is a logical aggregate of source code with a shared module name (namespace).
A module is just a folder with
*.v
files. All files within that folder must declare the same module name using themodule mod_name
statement.Modules can be organized intuitively using folders like
/google/api
,/google/services/directory
,/google/services/gmail
. This is a very natural and organic approach to managing code and is used extensibly in@vlib
.The rules to organize modules in folder are as follows:
*.v
of the module are organized together in one single folder for the module.*.v
files in the module's folder must have the same module name. The name of a module is short and concise. It must follow the snake_case naming convention.*.v
files is consider a module. We can have folders strictly for the purpose of organizing the modules themselves without the need to have code in them. For examples './google/services' are plain folders without code and inside we could have './google/services/directory' and './google/services/gmail' as modules with code.v.mod
file to a module, turns the module into a VPM package. Thev.mod
file is used to provide information to VPM.v.mod
creates a dummy package that can not be used with VPM, it is just a regular module..v.mod
will be used to anchor the qualified module name (QMN) (defines the module boundary).google/
) is the package folder which is also the repo and module name. VPM will continue to treat package as a single unit, regardless of its folder hierarchy and sub-modules.Resolving import names:
The module source name is the name specified with the
import
keyword.The alias (
mod.shortname
) is the las segment of the import name or as specified with the as keyword. The alias is the name used in code.The module source name is converted to a relative path (
.
replaced withos.path_separator
).A root folder is a location where we spect to find modules. These are:
pref.lookup_path
which may include @vlib, @vmodules but not necessary. The bulk of the imports will be from here. Should we forcevlib/
?v run ~/myproject
then~/myproject
is the project folder.modules/
folder in the project.modules/
folder of the file doing the import.In addition to
v.mod
, VPM adds '.git', '.hg', '.svn' as indicators of a module's boundary. These were not added. We feel that usingv.mod
is a simple solution when need to anchor to another path that is not one of the root folders. Also we should discuss the uses cases for.v.mod.stop
if we want to add it.The resolution starts with paths in
pref.lookup_path
which may include @vlib, @vmodules but not necessary. The bulk of the imports will be from here. Should we forcevlib/
?Lastly we search relative to the project folder (if pref.path is a valid folder, else we use
os.getwd()
in lieu of the project folder).The QMN (qualified module name) is always anchored to a root folder. The qualified module name will not extend beyond the anchor boundary. Like existing functions we walk the folder backwards until we reach a stop location.
When the resolution occurs in the context of a
module name
statement (module_decl()
) we take some shortcuts since we know thatmodule a
can only occur inside foldera
, we are already inside the module's folder.Code Changes
resolve_module function
The
resolve_module()
function invlib/v/util/module.v
attempts to provide a simple and reliable source of information for modules.It addressed various concerns and comments left in the code:
qualify_import()
fails to resolve almost everything.ModFileCacher
but using a much simpler code (IMHO). In particular we no longer need to worry about search paths.The function
resolve_module()
returns three strings,qualified_mod_name
,mod_directory
,mod_vmod_file
. It has the same parameters asqualify_import()/qualify_module()
but added a third boolean to indicate if we are resolving in the context of amodule
statement.The function implements a simple cache using the resolved path.
Use
-v
to output basic info about the resolution. Compile with-d debug_mod_resolve
to see more info.Other changes
The
parser.import_stmt()
andparser.module_decl()
were modified to useresolve_module()
, replacing call toqualify_import()/qualify_module()
. Modified to reduce code and num ofast.Import
created.The function
builder.parse_imports()
was modified to useresolve_module()
instead ofbuilder.find_module_path()
.the flag
-d trace_util_qualify
is no longer used.To discuss
Notes
ToDo
Support
pref.exclude[]
(expand_exclude_paths()
).Discuss supporting Go's
internal
concept. DiscussionRescue and investigate daa5be4. What was the issue? seems related to using os.path_separator in Windows.
Beta Was this translation helpful? Give feedback.
All reactions