@@ -1258,7 +1258,34 @@ impl Interpreter {
12581258 // Apply with modifiers.
12591259 for wm in & stmt. with_mods {
12601260 let path = Parser :: get_path_ref_components ( & wm. refr ) ?;
1261- let path: Vec < & str > = path. iter ( ) . map ( |s| s. text ( ) ) . collect ( ) ;
1261+ let mut path: Vec < String > = path. iter ( ) . map ( |s| s. text ( ) . to_string ( ) ) . collect ( ) ;
1262+
1263+ // Matching OPA, a leading import alias is rewritten before
1264+ // any lookups: functions register as overrides below,
1265+ // anything else becomes a data override. Only the alias
1266+ // component is replaced so bracketed keys containing dots
1267+ // survive the rewrite.
1268+ let rewritten: Option < Vec < String > > = match path. split_first ( ) {
1269+ Some ( ( head, rest) ) if head. as_str ( ) != "data" => {
1270+ self . lookup_import ( head) . and_then ( |import_expr| {
1271+ // Use the import target's parsed components, not
1272+ // its dot-joined string, so bracketed keys
1273+ // containing dots survive in the import path too.
1274+ let comps = Parser :: get_path_ref_components ( import_expr) . ok ( ) ?;
1275+ Some (
1276+ comps
1277+ . iter ( )
1278+ . map ( |s| s. text ( ) . to_string ( ) )
1279+ . chain ( rest. iter ( ) . cloned ( ) )
1280+ . collect ( ) ,
1281+ )
1282+ } )
1283+ }
1284+ _ => None ,
1285+ } ;
1286+ if let Some ( new_path) = rewritten {
1287+ path = new_path;
1288+ }
12621289 let mut target = path. join ( "." ) ;
12631290
12641291 let mut target_is_function = self . lookup_function_by_name ( & target) . is_some ( )
@@ -1297,11 +1324,17 @@ impl Interpreter {
12971324 if self . lookup_function_by_name ( & function_path) . is_none ( ) {
12981325 // Lookup without current module path prefixed.
12991326 function_path = get_path_string ( & wm. r#as , None ) ?;
1300- if self . lookup_function_by_name ( & function_path) . is_none ( )
1301- && !Self :: is_builtin ( wm. r#as . span ( ) , & function_path)
1302- {
1303- // bail!(wm.r#as.span().error("could not evaluate expression"));
1304- skip_exec = true ;
1327+ if self . lookup_function_by_name ( & function_path) . is_none ( ) {
1328+ // Resolve an aliased replacement before builtins.
1329+ let resolved = self
1330+ . resolve_fcn_path_through_imports ( & function_path)
1331+ . filter ( |r| self . compiled_policy . functions . contains_key ( r) ) ;
1332+ if let Some ( resolved) = resolved {
1333+ function_path = resolved;
1334+ } else if !Self :: is_builtin ( wm. r#as . span ( ) , & function_path) {
1335+ // bail!(wm.r#as.span().error("could not evaluate expression"));
1336+ skip_exec = true ;
1337+ }
13051338 }
13061339 }
13071340 self . with_functions
@@ -2371,6 +2404,72 @@ impl Interpreter {
23712404 }
23722405 }
23732406
2407+ /// Look up the import of the current module with the given alias, e.g.
2408+ /// the `data.a.b` import expression for `b` after `import data.a.b`.
2409+ fn lookup_import ( & self , alias : & str ) -> Option < & Ref < Expr > > {
2410+ if self . compiled_policy . imports . is_empty ( ) {
2411+ return None ;
2412+ }
2413+ let import_key = format ! ( "{}.{}" , self . current_module_path, alias) ;
2414+ self . compiled_policy . imports . get ( & import_key)
2415+ }
2416+
2417+ /// Look up the dot-joined target path of an import of the current module
2418+ /// with the given alias, e.g. `data.a.b` for `b` after `import data.a.b`.
2419+ fn lookup_import_alias ( & self , alias : & str ) -> Option < String > {
2420+ get_path_string ( self . lookup_import ( alias) ?, None ) . ok ( )
2421+ }
2422+
2423+ /// Rewrite a path whose leading component is an import alias of the
2424+ /// current module to the import's target, e.g. `b.f` to `data.a.b.f`
2425+ /// after `import data.a.b`.
2426+ fn rewrite_path_through_imports ( & self , path : & str ) -> Option < String > {
2427+ if path. starts_with ( "data." ) {
2428+ return None ;
2429+ }
2430+
2431+ let ( alias, rest) = match path. split_once ( '.' ) {
2432+ Some ( ( alias, rest) ) => ( alias, Some ( rest) ) ,
2433+ None => ( path, None ) ,
2434+ } ;
2435+ let target = self . lookup_import_alias ( alias) ?;
2436+ Some ( match rest {
2437+ Some ( rest) => format ! ( "{target}.{rest}" ) ,
2438+ None => target,
2439+ } )
2440+ }
2441+
2442+ /// Rewrite an import-aliased call path to its target, e.g. `b.f(1)` to
2443+ /// `data.a.b.f` after `import data.a.b`. Resolves only when the target is
2444+ /// a known function or default function, so an alias whose target defines
2445+ /// the called function shadows a like-named builtin namespace, while other
2446+ /// spellings keep their prior meaning (e.g. a builtin call). OPA instead
2447+ /// rewrites aliases unconditionally and rejects calls to a missing target
2448+ /// at compile time.
2449+ fn resolve_fcn_path_through_imports ( & self , path : & str ) -> Option < String > {
2450+ let candidate = self . rewrite_path_through_imports ( path) ?;
2451+ ( self . compiled_policy . functions . contains_key ( & candidate)
2452+ || self . is_default_function ( & candidate) )
2453+ . then_some ( candidate)
2454+ }
2455+
2456+ /// True if `path` is the exact path of a `default` function rule.
2457+ /// `default_rules` also indexes every prefix of a rule path, so it cannot
2458+ /// be consulted alone: `rule_paths` holds only exact rule paths, and the
2459+ /// non-empty argument list distinguishes functions from value rules.
2460+ fn is_default_function ( & self , path : & str ) -> bool {
2461+ self . compiled_policy . rule_paths . contains ( path)
2462+ && self
2463+ . compiled_policy
2464+ . default_rules
2465+ . get ( path)
2466+ . is_some_and ( |rules| {
2467+ rules. iter ( ) . any ( |( rule, _) | {
2468+ matches ! ( rule. as_ref( ) , Rule :: Default { args, .. } if !args. is_empty( ) )
2469+ } )
2470+ } )
2471+ }
2472+
23742473 fn eval_builtin_call (
23752474 & mut self ,
23762475 span : & Span ,
@@ -2542,6 +2641,13 @@ impl Interpreter {
25422641 param_values. push ( self . eval_expr ( p) ?) ;
25432642 }
25442643
2644+ // Resolve a leading import alias before the `with` override and builtin
2645+ // lookups, so an override keyed by the full path reaches aliased calls
2646+ // and the alias shadows a like-named builtin namespace (matching OPA).
2647+ let fcn_path = self
2648+ . resolve_fcn_path_through_imports ( & fcn_path)
2649+ . unwrap_or ( fcn_path) ;
2650+
25452651 let orig_fcn_path = fcn_path. clone ( ) ;
25462652
25472653 let mut with_functions_saved = None ;
@@ -2718,7 +2824,12 @@ impl Interpreter {
27182824 let value = match self . eval_rule_bodies ( ctx, span, bodies) {
27192825 Ok ( v) => v,
27202826 Err ( e) => {
2721- // If the rule produces an error, save the error.
2827+ // If the rule produces an error, save the error. Restore
2828+ // the caller's module even so: leaving the callee's module
2829+ // in place would make the rest of the caller's body
2830+ // resolve paths through the wrong module's imports when
2831+ // the error is swallowed below in non-strict mode.
2832+ self . set_current_module ( prev_module) ?;
27222833 errors. push ( e) ;
27232834 self . scopes = scopes;
27242835 continue ;
0 commit comments