Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
c5a7f04
updated slot types and included id within entity reference
alanwang67 Jun 23, 2025
cf5f7a1
make imports consistent
alanwang67 Jun 23, 2025
92db238
added comment
alanwang67 Jun 23, 2025
6e439ce
updates to proto policy.rs, currently doesn't account for generalized…
alanwang67 Jun 24, 2025
0a9a000
added comments & edited function to return a pointer
alanwang67 Jun 24, 2025
6dc4a83
changed to using as_ref
alanwang67 Jun 24, 2025
687984e
updates to Cedar's templates & policy structures to now store general…
alanwang67 Jun 27, 2025
ba8691f
removed comment
alanwang67 Jun 27, 2025
9878233
clippy lint fixes
alanwang67 Jun 27, 2025
381ed42
changed into to use from
alanwang67 Jun 27, 2025
697df9b
changed unwrap to expect
alanwang67 Jun 27, 2025
b446dfd
generalized templates with schemas
alanwang67 Jul 8, 2025
620d0e2
updates to validator generalized slots annotation structure
alanwang67 Jul 8, 2025
ff3a4bd
fixed some feature flag errors & refactored typechecker
alanwang67 Jul 8, 2025
2863d73
updates to protobuf formats
alanwang67 Jul 9, 2025
5aac5e5
Added test for typechecking of types that shadow primitive types
alanwang67 Jul 9, 2025
ca8b85c
fixed loop
alanwang67 Jul 9, 2025
d16a6e7
fixed test that failed because of schema change
alanwang67 Jul 9, 2025
40c69e6
put back function that is used in cedar symcc
alanwang67 Jul 16, 2025
bcbec32
updates to remove generalized slots from appearing in the scope of th…
alanwang67 Jul 28, 2025
9e2073e
added some more test cases for namespaces and included schema for lin…
alanwang67 Jul 28, 2025
564a027
updates to error handling to reflect new changes
alanwang67 Jul 28, 2025
6880e82
link time type checking updates
alanwang67 Jul 29, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ fn handle_principal_scope(policy: &Template, info: &ScopeVariableInfo) -> Comple
PrincipalOrResourceConstraint::Eq(entity_reference) => create_binary_op_context(
Op::eq(),
principal_var,
entity_reference.into_expr(SlotId::principal()).into(),
entity_reference.into_expr(SlotId::resource()).into(),
),

PrincipalOrResourceConstraint::Is(..) => create_is_context(principal_var),
Expand Down
6 changes: 3 additions & 3 deletions cedar-language-server/src/schema/fold.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,15 +79,15 @@ pub(crate) fn fold_schema(schema_info: &SchemaInfo) -> Option<Vec<FoldingRange>>
.entity_types()
.filter_map(|et| et.loc.as_loc_ref());
let action_locs = validator.action_ids().filter_map(|a| a.loc());
let common_types = validator
.common_types()
let common_types_extended = validator
.common_types_extended()
.filter_map(|ct| ct.type_loc.as_loc_ref());

// Combine all locations and create folding ranges
let ranges = namespace_locs
.chain(entity_type_locs)
.chain(action_locs)
.chain(common_types)
.chain(common_types_extended)
.unique()
.map(|loc| {
let src_range = loc.to_range();
Expand Down
6 changes: 3 additions & 3 deletions cedar-language-server/src/schema/symbols.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,8 @@ pub(crate) fn schema_symbols(schema_info: &SchemaInfo) -> Option<Vec<DocumentSym
.collect();

// Create common type symbols
let common_type_symbols: Vec<DocumentSymbol> = validator
.common_types()
let common_type_extended_symbols: Vec<DocumentSymbol> = validator
.common_types_extended()
.filter_map(|ct| {
ct.name_loc
.as_ref()
Expand All @@ -144,7 +144,7 @@ pub(crate) fn schema_symbols(schema_info: &SchemaInfo) -> Option<Vec<DocumentSym
let mut all_other_symbols = Vec::new();
all_other_symbols.extend(entity_type_symbols);
all_other_symbols.extend(action_symbols);
all_other_symbols.extend(common_type_symbols);
all_other_symbols.extend(common_type_extended_symbols);

// Assign symbols to namespaces based on range intersection
let mut remaining_symbols = Vec::new();
Expand Down
2 changes: 2 additions & 0 deletions cedar-policy-core/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,7 @@ mod expr_iterator;
pub use expr_iterator::*;
mod annotation;
pub use annotation::*;
mod generalized_slots_annotation;
pub use generalized_slots_annotation::*;
mod expr_visitor;
pub use expr_visitor::*;
18 changes: 16 additions & 2 deletions cedar-policy-core/src/ast/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -293,13 +293,27 @@ impl<T> Expr<T> {
self.subexpressions()
.filter_map(|exp| match &exp.expr_kind {
ExprKind::Slot(slotid) => Some(Slot {
id: *slotid,
id: slotid.clone(),
loc: exp.source_loc().into_maybe_loc(),
}),
_ => None,
})
}

/// Iterate over all of the principal or resource slots in this policy AST
pub fn principal_or_resource_slots(&self) -> impl Iterator<Item = Slot> + '_ {
self.subexpressions()
.filter_map(|exp| match &exp.expr_kind {
ExprKind::Slot(slotid) if slotid.is_principal() || slotid.is_resource() => {
Some(Slot {
id: slotid.clone(),
loc: exp.source_loc().into_maybe_loc(),
})
}
_ => None,
})
}

/// Determine if the expression is projectable under partial evaluation
/// An expression is projectable if it's guaranteed to never error on evaluation
/// This is true if the expression is entirely composed of values or unknowns
Expand Down Expand Up @@ -1842,7 +1856,7 @@ mod test {
let e = Expr::slot(SlotId::principal());
let p = SlotId::principal();
let r = SlotId::resource();
let set: HashSet<SlotId> = HashSet::from_iter([p]);
let set: HashSet<SlotId> = HashSet::from_iter([p.clone()]);
assert_eq!(set, e.slots().map(|slot| slot.id).collect::<HashSet<_>>());
let e = Expr::or(
Expr::slot(SlotId::principal()),
Expand Down
2 changes: 1 addition & 1 deletion cedar-policy-core/src/ast/expr_visitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ pub trait ExprVisitor {
match expr.expr_kind() {
ExprKind::Lit(lit) => self.visit_literal(lit, loc),
ExprKind::Var(var) => self.visit_var(*var, loc),
ExprKind::Slot(slot) => self.visit_slot(*slot, loc),
ExprKind::Slot(slot) => self.visit_slot(slot.clone(), loc),
ExprKind::Unknown(unknown) => self.visit_unknown(unknown, loc),
ExprKind::If {
test_expr,
Expand Down
119 changes: 119 additions & 0 deletions cedar-policy-core/src/ast/generalized_slots_annotation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*
* Copyright Cedar Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

use std::collections::BTreeMap;

use crate::ast::SlotId;
use crate::extensions::Extensions;
use crate::validator::{
json_schema::Type as JSONSchemaType, types::Type as ValidatorType, RawName, SchemaError,
ValidatorSchema,
};
use serde::{Deserialize, Serialize};
use serde_with::serde_as;

/// Struct which holds the type & position of a generalized slot
#[derive(Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Hash, Serialize, Deserialize)]
#[serde_as]
pub struct GeneralizedSlotsAnnotation(BTreeMap<SlotId, JSONSchemaType<RawName>>);

impl GeneralizedSlotsAnnotation {
/// Create a new empty `GeneralizedSlotsAnnotation` (with no slots)
pub fn new() -> Self {
Self(BTreeMap::new())
}

/// Get the type of the slot by key
pub fn get(&self, key: &SlotId) -> Option<&JSONSchemaType<RawName>> {
self.0.get(key)
}

/// Iterate over all pairs of slots and their types
pub fn iter(&self) -> impl Iterator<Item = (&SlotId, &JSONSchemaType<RawName>)> {
self.0.iter()
}

/// Tell if it's empty
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}

pub(crate) fn into_validator_generalized_slots_annotation(
self,
schema: &ValidatorSchema,
) -> Result<ValidatorGeneralizedSlotsAnnotation, SchemaError> {
let validator_generalized_slots_annotation: Result<BTreeMap<_, _>, SchemaError> = self
.0
.into_iter()
.map(|(k, ty)| -> Result<_, SchemaError> {
Ok((
k,
schema.json_schema_type_to_validator_type(ty, Extensions::all_available())?,
))
})
.collect();
Ok(validator_generalized_slots_annotation?.into())
}
}

impl Default for GeneralizedSlotsAnnotation {
fn default() -> Self {
Self::new()
}
}

impl FromIterator<(SlotId, JSONSchemaType<RawName>)> for GeneralizedSlotsAnnotation {
fn from_iter<T: IntoIterator<Item = (SlotId, JSONSchemaType<RawName>)>>(iter: T) -> Self {
Self(BTreeMap::from_iter(iter))
}
}

impl From<BTreeMap<SlotId, JSONSchemaType<RawName>>> for GeneralizedSlotsAnnotation {
fn from(value: BTreeMap<SlotId, JSONSchemaType<RawName>>) -> Self {
Self(value)
}
}

#[derive(Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Hash)]
pub(crate) struct ValidatorGeneralizedSlotsAnnotation(BTreeMap<SlotId, ValidatorType>);

impl FromIterator<(SlotId, ValidatorType)> for ValidatorGeneralizedSlotsAnnotation {
fn from_iter<T: IntoIterator<Item = (SlotId, ValidatorType)>>(iter: T) -> Self {
Self(BTreeMap::from_iter(iter))
}
}

impl From<BTreeMap<SlotId, ValidatorType>> for ValidatorGeneralizedSlotsAnnotation {
fn from(value: BTreeMap<SlotId, ValidatorType>) -> Self {
Self(value)
}
}

impl Default for ValidatorGeneralizedSlotsAnnotation {
fn default() -> Self {
Self::new()
}
}

impl ValidatorGeneralizedSlotsAnnotation {
pub(crate) fn new() -> Self {
Self(BTreeMap::new())
}

pub(crate) fn get(&self, slot: &SlotId) -> Option<&ValidatorType> {
self.0.get(slot)
}
}
40 changes: 34 additions & 6 deletions cedar-policy-core/src/ast/name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use miette::Diagnostic;
use ref_cast::RefCast;
use regex::Regex;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_with::{serde_as, DisplayFromStr};
use smol_str::ToSmolStr;
use std::collections::HashSet;
use std::fmt::Display;
Expand Down Expand Up @@ -283,9 +284,10 @@ impl<'de> Deserialize<'de> for InternalName {
/// Clone is O(1).
// This simply wraps a separate enum -- currently [`ValidSlotId`] -- in case we
// want to generalize later
#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde_as]
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct SlotId(pub(crate) ValidSlotId);
pub struct SlotId(#[serde_as(as = "DisplayFromStr")] pub(crate) ValidSlotId);

impl SlotId {
/// Get the slot for `principal`
Expand All @@ -298,6 +300,11 @@ impl SlotId {
Self(ValidSlotId::Resource)
}

/// Create a `generalized slot`
pub fn generalized_slot(id: Id) -> Self {
Self(ValidSlotId::GeneralizedSlot(id))
}

/// Check if a slot represents a principal
pub fn is_principal(&self) -> bool {
matches!(self, Self(ValidSlotId::Principal))
Expand All @@ -307,6 +314,11 @@ impl SlotId {
pub fn is_resource(&self) -> bool {
matches!(self, Self(ValidSlotId::Resource))
}

/// Check if a slot represents a generalized slot
pub fn is_generalized_slot(&self) -> bool {
matches!(self, Self(ValidSlotId::GeneralizedSlot(_)))
}
}

impl From<PrincipalOrResource> for SlotId {
Expand All @@ -318,31 +330,47 @@ impl From<PrincipalOrResource> for SlotId {
}
}

impl FromStr for SlotId {
type Err = ParseErrors;

fn from_str(s: &str) -> Result<Self, Self::Err> {
crate::parser::parse_slot(s).map(SlotId)
}
}

impl std::fmt::Display for SlotId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}

/// Two possible variants for Slots
#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
/// Three possible variants for Slots
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub(crate) enum ValidSlotId {
#[serde(rename = "?principal")]
Principal,
#[serde(rename = "?resource")]
Resource,
GeneralizedSlot(Id), // Slots for generalized templates, for more info see [RFC 98](https://github.com/cedar-policy/rfcs/pull/98).
}

impl std::fmt::Display for ValidSlotId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
ValidSlotId::Principal => "principal",
ValidSlotId::Resource => "resource",
ValidSlotId::GeneralizedSlot(id) => id.as_ref(),
};
write!(f, "?{s}")
}
}

impl FromStr for ValidSlotId {
type Err = ParseErrors;

fn from_str(s: &str) -> Result<Self, Self::Err> {
crate::parser::parse_slot(s)
}
}

/// [`SlotId`] plus a source location
#[derive(Educe, Debug, Clone)]
#[educe(PartialEq, Eq, Hash)]
Expand Down
Loading