Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion src/languages/rego/compiler/references.rs
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,18 @@ impl<'a> Compiler<'a> {
}
}

// No rule found - undefined variable
// No rule found; fall back to module-level imports.
let import_key = format!("{}.{}", &self.current_package, root);
if let Some(import_expr) = self.policy.inner.imports.get(&import_key) {
let import_reg =
self.compile_rego_expr_with_span(import_expr, import_expr.span(), false)?;
if chain.components.is_empty() {
return Ok(import_reg);
}
return self.compile_chain_access(import_reg, &chain.components, span);
}

// No rule or import found - undefined variable
Err(CompilerError::UndefinedVariable {
name: root.to_string(),
}
Expand Down
26 changes: 22 additions & 4 deletions src/scheduler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -337,8 +337,9 @@ impl Analyzer {
}

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

// Add import aliases specific to this module
for import in &m.imports {
if let Some(var) = &import.r#as {
crate::Rc::make_mut(&mut module_globals).insert(var.text().to_string());
if let Some(alias_span) = import_alias_span(import) {
crate::Rc::make_mut(&mut module_globals).insert(alias_span.text().to_string());
}
}

Expand Down Expand Up @@ -1198,3 +1199,20 @@ pub fn compute_module_globals(

Ok(result)
}

// Extract the binding name an import contributes, even without an explicit `as` clause.
fn import_alias_span(import: &Import) -> Option<Span> {
if let Some(alias) = &import.r#as {
return Some(alias.clone());
}

match import.refr.as_ref() {
RefDot { field, .. } => Some(field.0.clone()),
RefBrack { index, .. } => match index.as_ref() {
Expr::String { span, .. } => Some(span.clone()),
_ => None,
},
Var { span, .. } => Some(span.clone()),
_ => None,
}
}
103 changes: 103 additions & 0 deletions tests/interpreter/cases/import/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,109 @@ cases:
a: 10
c: 22
r: true
- note: import data inside rule body
modules:
- |
package lib
import rego.v1

a_value := "a_value"

- |
package rules
import data.lib
import rego.v1

sample if {
lib.a_value == "a_value"
}
query: data.rules.sample
want_result: true

- note: import data alias
modules:
- |
package lib
import rego.v1

value := "a_value"

- |
package rules
import data.lib as mylib
import rego.v1

sample if {
mylib.value == "a_value"
}
query: data.rules.sample
want_result: true

- note: import nested package chain
modules:
- |
package lib.inner
import rego.v1

nested := {"key": "value"}

- |
package rules
import data.lib.inner
import rego.v1

lookup := value if {
value := inner.nested.key
}
query: data.rules.lookup
want_result: "value"

- note: import alias shadowed by rule
modules:
- |
package lib
import rego.v1

value := "from data"

- |
package rules
import data.lib as lib_alias
import rego.v1

lib_alias := {"value": "from rule"}

# TODO:
# OPA currently reports this rule as undefined. This implies that
# lib_alias retains the imported value eventhough there is a rule
# with same name. In regorus, the rule takes precedence.
# Needs investigation to figure out which behavior is correct.
shadow if {
lib_alias.value == "from rule"
}
query: data.rules.shadow
want_result: true


- note: import used in comprehension
modules:
- |
package lib
import rego.v1

dataset := {"a", "b"}

- |
package rules
import data.lib
import rego.v1

present if {
some item in lib.dataset
item == "b"
}
query: data.rules.present
want_result: true

- note: import overridden by rule
modules:
Expand Down
97 changes: 97 additions & 0 deletions tests/rvm/rego/cases/imports.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.

cases:
- note: import_data_inside_rule_body
modules:
- |
package lib
import rego.v1

a_value := "a_value"

- |
package rules
import data.lib
import rego.v1

sample if {
lib.a_value == "a_value"
}
query: data.rules.sample
want_result: true
- note: import_data_alias
modules:
- |
package lib
import rego.v1

value := "a_value"

- |
package rules
import data.lib as mylib
import rego.v1

sample if {
mylib.value == "a_value"
}
query: data.rules.sample
want_result: true
- note: import_nested_package_chain
modules:
- |
package lib.inner
import rego.v1

nested := {"key": "value"}

- |
package rules
import data.lib.inner
import rego.v1

lookup := value if {
value := inner.nested.key
}
query: data.rules.lookup
want_result: "value"
- note: import_alias_shadowed_by_rule
modules:
- |
package lib
import rego.v1

value := "from data"

- |
package rules
import data.lib as lib_alias
import rego.v1

lib_alias := {"value": "from rule"}

shadow if {
lib_alias.value == "from rule"
}
query: data.rules.shadow
want_result: true
- note: import_used_in_comprehension
modules:
- |
package lib
import rego.v1

dataset := {"a", "b"}

- |
package rules
import data.lib
import rego.v1

present if {
some item in lib.dataset
item == "b"
}
query: data.rules.present
want_result: true
Loading