// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #![allow(clippy::pattern_type_mismatch)] //! Field-kind and resource-path compilation. use alloc::format; use alloc::string::{String, ToString as _}; use alloc::vec::Vec; use anyhow::{anyhow, bail, Result}; use crate::languages::azure_policy::ast::{Expr, ExprLiteral, FieldKind}; use crate::rvm::instructions::{LoopMode, LoopStartParams}; use crate::rvm::Instruction; use super::core::Compiler; use super::utils::{split_count_wildcard_path, split_path_without_wildcards}; impl Compiler { pub(super) fn compile_field_kind( &mut self, kind: &FieldKind, span: &crate::lexer::Span, ) -> Result { let reg = match kind { FieldKind::Type => { self.record_field_kind("type"); self.compile_resource_path_value("type", span)? } FieldKind::Id => { self.record_field_kind("id"); self.compile_resource_path_value("id", span)? } FieldKind::Kind => { self.record_field_kind("kind"); self.compile_resource_path_value("kind", span)? } FieldKind::Name => { self.record_field_kind("name"); self.compile_resource_path_value("name", span)? } FieldKind::Location => { self.record_field_kind("location"); self.compile_resource_path_value("location", span)? } FieldKind::FullName => { self.record_field_kind("fullName"); self.compile_resource_path_value("fullName", span)? } FieldKind::Tags => { self.record_field_kind("tags"); self.compile_resource_path_value("tags", span)? } FieldKind::IdentityType => { self.record_field_kind("identity.type"); self.compile_resource_path_value("identity.type", span)? } FieldKind::IdentityField(ref subpath) => { let path = format!("identity.{}", subpath.to_ascii_lowercase()); self.record_field_kind(&path); self.compile_resource_path_value(&path, span)? } FieldKind::ApiVersion => { self.record_field_kind("apiVersion"); self.compile_resource_path_value("apiVersion", span)? } FieldKind::Tag(tag) => { self.record_field_kind("tags"); self.record_tag_name(tag); let tag_lower = tag.to_ascii_lowercase(); if let Some(override_reg) = self.resource_override_reg { self.emit_chained_index_literal_path(override_reg, &["tags", &tag_lower], span)? } else { let input_reg = self.load_input(span)?; self.emit_chained_index_literal_path( input_reg, &["resource", "tags", &tag_lower], span, )? } } FieldKind::Alias(path) => { self.record_alias(path); let short = self.resolve_alias_path(path, span)?; self.compile_field_path_expression(&short, span)? } FieldKind::Expr(expr) => self.compile_dynamic_field_expr(expr, span)?, }; Ok(reg) } /// Compile a dynamic field expression (`FieldKind::Expr`). fn compile_dynamic_field_expr(&mut self, expr: &Expr, span: &crate::lexer::Span) -> Result { if let Expr::Call { func, args, .. } = expr { if let Expr::Ident { name, .. } = func.as_ref() { if name.eq_ignore_ascii_case("if") { if let [cond_arg, Expr::Literal { value: ExprLiteral::String(alias_a), .. }, Expr::Literal { value: ExprLiteral::String(alias_b), .. }] = args.as_slice() { self.record_alias(alias_a); self.record_alias(alias_b); let short_a = self.resolve_alias_path(alias_a, span)?; let short_b = self.resolve_alias_path(alias_b, span)?; let cond_reg = self.compile_expr(cond_arg)?; let then_reg = self.compile_field_path_expression(&short_a, span)?; self.emit_coalesce_undefined_to_null(then_reg, span); let else_reg = self.compile_field_path_expression(&short_b, span)?; self.emit_coalesce_undefined_to_null(else_reg, span); return self.emit_builtin_call( "azure.policy.if", &[cond_reg, then_reg, else_reg], span, ); } } } } // Handle concat() that produces a tag path. if let Expr::Call { func, args, .. } = expr { if let Expr::Ident { name, .. } = func.as_ref() { if name.eq_ignore_ascii_case("concat") && !args.is_empty() { if let Some(Expr::Literal { value: ExprLiteral::String(first), .. }) = args.first() { if first == "tags" || first.starts_with("tags.") || first.starts_with("tags[") { self.observed_has_dynamic_fields = true; let path_reg = self.compile_expr(expr)?; let resource_reg = self.compile_resource_root(span)?; return self.emit_builtin_call( "azure.policy.resolve_field", &[resource_reg, path_reg], span, ); } } } } } bail!(span.error( "unsupported dynamic field expression; only \ `if(cond, 'alias', 'alias')` and `concat('tags...', ...)` \ patterns are supported", )); } pub(super) fn compile_field_path_expression( &mut self, field_path: &str, span: &crate::lexer::Span, ) -> Result { if let Some(binding) = self.resolve_count_binding(field_path)? { return self.compile_from_binding(&binding, field_path, span); } if field_path.contains("[*]") { return self.compile_field_wildcard_collect(field_path, span); } self.compile_resource_path_value(field_path, span) } pub(super) fn compile_resource_path_value( &mut self, field_path: &str, span: &crate::lexer::Span, ) -> Result { let lowered = field_path.to_ascii_lowercase(); if let Some(override_reg) = self.resource_override_reg { let parts = split_path_without_wildcards(&lowered)?; let refs = parts.iter().map(String::as_str).collect::>(); return self.emit_chained_index_literal_path(override_reg, &refs, span); } let input_reg = self.load_input(span)?; let mut path = Vec::new(); path.push("resource".to_string()); for part in split_path_without_wildcards(&lowered)? { path.push(part); } let refs = path.iter().map(String::as_str).collect::>(); self.emit_chained_index_literal_path(input_reg, &refs, span) } pub(super) fn compile_resource_root(&mut self, span: &crate::lexer::Span) -> Result { if let Some(override_reg) = self.resource_override_reg { return Ok(override_reg); } let input_reg = self.load_input(span)?; self.emit_chained_index_literal_path(input_reg, &["resource"], span) } // -- wildcard collection ----------------------------------------------- pub(super) fn compile_field_wildcard_collect( &mut self, field_path: &str, span: &crate::lexer::Span, ) -> Result { let result_reg = self.alloc_register()?; self.emit(Instruction::ArrayNew { dest: result_reg }, span); self.compile_wildcard_collect_inner(None, field_path, result_reg, span)?; Ok(result_reg) } fn compile_wildcard_collect_inner( &mut self, base_reg: Option, remaining_path: &str, result_reg: u8, span: &crate::lexer::Span, ) -> Result<()> { let (prefix, suffix) = split_count_wildcard_path(remaining_path)?; let prefix_lower = prefix.to_ascii_lowercase(); let collection_reg = match base_reg { Some(base) if prefix_lower.is_empty() => base, Some(base) => { let parts = split_path_without_wildcards(&prefix_lower)?; let refs = parts.iter().map(String::as_str).collect::>(); self.emit_chained_index_literal_path(base, &refs, span)? } None if prefix_lower.is_empty() => self.compile_resource_root(span)?, None => self.compile_resource_path_value(&prefix_lower, span)?, }; let key_reg = self.alloc_register()?; let current_reg = self.alloc_register()?; let loop_result_reg = self.alloc_register()?; let params_index = self.program.add_loop_params(LoopStartParams { mode: LoopMode::ForEach, collection: collection_reg, key_reg, value_reg: current_reg, result_reg: loop_result_reg, body_start: 0, loop_end: 0, }); self.emit(Instruction::LoopStart { params_index }, span); let body_start = u16::try_from(self.program.instructions.len()) .map_err(|_| anyhow!("instruction index overflow"))?; match suffix { Some(ref s) if s.contains("[*]") => { self.compile_wildcard_collect_inner(Some(current_reg), s, result_reg, span)?; } Some(ref s) => { let s_lower = s.to_ascii_lowercase(); let parts = split_path_without_wildcards(&s_lower)?; let refs = parts.iter().map(String::as_str).collect::>(); let val_reg = self.emit_chained_index_literal_path(current_reg, &refs, span)?; self.emit( Instruction::ArrayPushDefined { arr: result_reg, value: val_reg, }, span, ); } None => { self.emit( Instruction::ArrayPushDefined { arr: result_reg, value: current_reg, }, span, ); } } self.emit( Instruction::LoopNext { body_start, loop_end: 0, }, span, ); let loop_end = u16::try_from(self.program.instructions.len()) .map_err(|_| anyhow!("instruction index overflow"))?; self.program.update_loop_params(params_index, |params| { params.body_start = body_start; params.loop_end = loop_end; }); if let Some(Instruction::LoopNext { loop_end: le, .. }) = self.program.instructions.last_mut() { *le = loop_end; } Ok(()) } }