Skip to content

jq: a module's own include directive is silently ignored (transitive include not processed) #2865

Description

@newhoggy

Severity: Low

Summary

Found while investigating #2774 (module-sourced $__loc__ provenance). A
module loaded via include can itself contain its own include directive,
naming a third module. Real jq processes that nested include transitively,
making the third module's defs available to the second module's own body.
succinctly's ModuleLoader does not: it only calls extract_func_defs on a
loaded module's parsed Program::expr, silently dropping that Program's own
includes/imports lists.

Repro

Confirmed live against jq 1.7.1:

$ cat inner.jq
def g: 42;
$ cat outer.jq
include "inner";
def h: g;

$ jq            -L . -nc 'include "outer"; h'   # 42
$ succinctly jq -L . -nc 'include "outer"; h'   # jq: error: g/0 is not defined at <top-level>

Root cause

ModuleLoader::ensure_module_loaded (src/bin/succinctly/jq_runner.rs) does:

let program = jq::parse_program(&contents)...?;
Ok(entry.insert(extract_func_defs(&program.expr)))

jq::parse_program returns a full Program (with its own includes,
imports, and module fields, exactly like the top-level parse), but only
program.expr is inspected. A module's own include/import directives are
parsed successfully and then discarded -- h's reference to g is therefore
genuinely undefined by the time succinctly's resolve pass sees it, since g
was never inlined into outer.jq's own body at all.

Why this is separate from #2774

#2774 is about what path a module-sourced $__loc__ reports once a module
is loaded; this is about a module failing to load its own transitive
dependencies at all, a functional gap rather than a diagnostic-wording one.

Suggested fix direction

ensure_module_loaded/load_module need to recurse: after parsing a
module's Program, resolve and inline that module's own includes (bare
names, merged into its scope) and imports (namespaced) before extracting
its function defs -- essentially the same logic ModuleLoader::process_program
already does for the top-level program, applied recursively to each loaded
module. Watch for cycles (a.jq includes b.jq includes a.jq) -- confirm
what jq itself does there (a compile error, most likely) before choosing a
behavior, and consider loaded_modules' existing per-path cache as a natural
place to detect an in-progress load.

Found via adversarial review of #2774 (PR pending).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    OpusSuitable for an Opus-class model to implementTriagedIssue has been read, planned, and its model-class label verified

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions