Skip to content

Commit 9426b2e

Browse files
authored
fix: Imports without a name binding (microsoft#543)
Handle imports that don't use the `as` clause to create a binding. These imports are bound to the last identifier in the imported path. Fix both interpreter and compiler. Add tests. fixes microsoft#541 Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
1 parent 740db8a commit 9426b2e

4 files changed

Lines changed: 234 additions & 5 deletions

File tree

src/languages/rego/compiler/references.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,18 @@ impl<'a> Compiler<'a> {
335335
}
336336
}
337337

338-
// No rule found - undefined variable
338+
// No rule found; fall back to module-level imports.
339+
let import_key = format!("{}.{}", &self.current_package, root);
340+
if let Some(import_expr) = self.policy.inner.imports.get(&import_key) {
341+
let import_reg =
342+
self.compile_rego_expr_with_span(import_expr, import_expr.span(), false)?;
343+
if chain.components.is_empty() {
344+
return Ok(import_reg);
345+
}
346+
return self.compile_chain_access(import_reg, &chain.components, span);
347+
}
348+
349+
// No rule or import found - undefined variable
339350
Err(CompilerError::UndefinedVariable {
340351
name: root.to_string(),
341352
}

src/scheduler.rs

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -337,8 +337,9 @@ impl Analyzer {
337337
}
338338

339339
for import in &m.imports {
340-
if let Some(var) = &import.r#as {
341-
scope.unscoped.insert(var.source_str());
340+
// Ensure default alias imports (e.g. import data.pkg.mod) are visible to this scope.
341+
if let Some(alias_span) = import_alias_span(import) {
342+
scope.unscoped.insert(alias_span.source_str());
342343
}
343344
}
344345
}
@@ -1167,8 +1168,8 @@ pub fn compute_module_globals(
11671168

11681169
// Add import aliases specific to this module
11691170
for import in &m.imports {
1170-
if let Some(var) = &import.r#as {
1171-
crate::Rc::make_mut(&mut module_globals).insert(var.text().to_string());
1171+
if let Some(alias_span) = import_alias_span(import) {
1172+
crate::Rc::make_mut(&mut module_globals).insert(alias_span.text().to_string());
11721173
}
11731174
}
11741175

@@ -1198,3 +1199,20 @@ pub fn compute_module_globals(
11981199

11991200
Ok(result)
12001201
}
1202+
1203+
// Extract the binding name an import contributes, even without an explicit `as` clause.
1204+
fn import_alias_span(import: &Import) -> Option<Span> {
1205+
if let Some(alias) = &import.r#as {
1206+
return Some(alias.clone());
1207+
}
1208+
1209+
match import.refr.as_ref() {
1210+
RefDot { field, .. } => Some(field.0.clone()),
1211+
RefBrack { index, .. } => match index.as_ref() {
1212+
Expr::String { span, .. } => Some(span.clone()),
1213+
_ => None,
1214+
},
1215+
Var { span, .. } => Some(span.clone()),
1216+
_ => None,
1217+
}
1218+
}

tests/interpreter/cases/import/tests.yaml

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,109 @@ cases:
5656
a: 10
5757
c: 22
5858
r: true
59+
- note: import data inside rule body
60+
modules:
61+
- |
62+
package lib
63+
import rego.v1
64+
65+
a_value := "a_value"
66+
67+
- |
68+
package rules
69+
import data.lib
70+
import rego.v1
71+
72+
sample if {
73+
lib.a_value == "a_value"
74+
}
75+
query: data.rules.sample
76+
want_result: true
77+
78+
- note: import data alias
79+
modules:
80+
- |
81+
package lib
82+
import rego.v1
83+
84+
value := "a_value"
85+
86+
- |
87+
package rules
88+
import data.lib as mylib
89+
import rego.v1
90+
91+
sample if {
92+
mylib.value == "a_value"
93+
}
94+
query: data.rules.sample
95+
want_result: true
96+
97+
- note: import nested package chain
98+
modules:
99+
- |
100+
package lib.inner
101+
import rego.v1
102+
103+
nested := {"key": "value"}
104+
105+
- |
106+
package rules
107+
import data.lib.inner
108+
import rego.v1
109+
110+
lookup := value if {
111+
value := inner.nested.key
112+
}
113+
query: data.rules.lookup
114+
want_result: "value"
115+
116+
- note: import alias shadowed by rule
117+
modules:
118+
- |
119+
package lib
120+
import rego.v1
121+
122+
value := "from data"
123+
124+
- |
125+
package rules
126+
import data.lib as lib_alias
127+
import rego.v1
128+
129+
lib_alias := {"value": "from rule"}
130+
131+
# TODO:
132+
# OPA currently reports this rule as undefined. This implies that
133+
# lib_alias retains the imported value eventhough there is a rule
134+
# with same name. In regorus, the rule takes precedence.
135+
# Needs investigation to figure out which behavior is correct.
136+
shadow if {
137+
lib_alias.value == "from rule"
138+
}
139+
query: data.rules.shadow
140+
want_result: true
141+
142+
143+
- note: import used in comprehension
144+
modules:
145+
- |
146+
package lib
147+
import rego.v1
148+
149+
dataset := {"a", "b"}
150+
151+
- |
152+
package rules
153+
import data.lib
154+
import rego.v1
155+
156+
present if {
157+
some item in lib.dataset
158+
item == "b"
159+
}
160+
query: data.rules.present
161+
want_result: true
59162

60163
- note: import overridden by rule
61164
modules:

tests/rvm/rego/cases/imports.yaml

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# Copyright (c) Microsoft Corporation.
2+
# Licensed under the MIT License.
3+
4+
cases:
5+
- note: import_data_inside_rule_body
6+
modules:
7+
- |
8+
package lib
9+
import rego.v1
10+
11+
a_value := "a_value"
12+
13+
- |
14+
package rules
15+
import data.lib
16+
import rego.v1
17+
18+
sample if {
19+
lib.a_value == "a_value"
20+
}
21+
query: data.rules.sample
22+
want_result: true
23+
- note: import_data_alias
24+
modules:
25+
- |
26+
package lib
27+
import rego.v1
28+
29+
value := "a_value"
30+
31+
- |
32+
package rules
33+
import data.lib as mylib
34+
import rego.v1
35+
36+
sample if {
37+
mylib.value == "a_value"
38+
}
39+
query: data.rules.sample
40+
want_result: true
41+
- note: import_nested_package_chain
42+
modules:
43+
- |
44+
package lib.inner
45+
import rego.v1
46+
47+
nested := {"key": "value"}
48+
49+
- |
50+
package rules
51+
import data.lib.inner
52+
import rego.v1
53+
54+
lookup := value if {
55+
value := inner.nested.key
56+
}
57+
query: data.rules.lookup
58+
want_result: "value"
59+
- note: import_alias_shadowed_by_rule
60+
modules:
61+
- |
62+
package lib
63+
import rego.v1
64+
65+
value := "from data"
66+
67+
- |
68+
package rules
69+
import data.lib as lib_alias
70+
import rego.v1
71+
72+
lib_alias := {"value": "from rule"}
73+
74+
shadow if {
75+
lib_alias.value == "from rule"
76+
}
77+
query: data.rules.shadow
78+
want_result: true
79+
- note: import_used_in_comprehension
80+
modules:
81+
- |
82+
package lib
83+
import rego.v1
84+
85+
dataset := {"a", "b"}
86+
87+
- |
88+
package rules
89+
import data.lib
90+
import rego.v1
91+
92+
present if {
93+
some item in lib.dataset
94+
item == "b"
95+
}
96+
query: data.rules.present
97+
want_result: true

0 commit comments

Comments
 (0)