diff --git a/src/annotations.rs b/src/annotations.rs new file mode 100644 index 0000000..b51da4a --- /dev/null +++ b/src/annotations.rs @@ -0,0 +1,439 @@ +use std::cmp::Ordering; +use std::collections::{BTreeMap, BTreeSet}; +use std::ops::Range; + +use codespan_reporting::files::SimpleFile; + +use crate::ast::{Annotation, Annotations, Argument, CascadeString}; +use crate::error::ErrorItem; +use crate::warning::{Warning, Warnings, WithWarnings}; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct AssociatedResource { + name: CascadeString, + doms: BTreeSet>, + ranges: BTreeMap>, +} + +impl AssociatedResource { + // Unlike most get_range() functions, this one takes an argument. An AssociatedResource + // possibly contains information about various associated points, so we need to know the name + // of the resource we want the range for + pub fn get_range(&self, resource_name: &str) -> Option> { + self.ranges.get(resource_name).cloned() + } + + pub fn name(&self) -> &CascadeString { + &self.name + } + + pub fn get_class_names(&self) -> Vec { + self.doms + .iter() + .map(|d| match d { + Some(d) => { + format!("{}.{}", d, &self.name) + } + None => self.name.to_string(), + }) + .collect() + } + + pub fn basename(&self) -> &str { + self.name.as_ref() + } + + // Return true if type_name is one of the resources that have been combined in this + // AssociatedResource + pub fn string_is_instance(&self, type_name: &CascadeString) -> bool { + match type_name.as_ref().split_once('.') { + Some((dom, res)) => { + res == self.name && self.doms.contains(&Some(CascadeString::from(dom))) + } + None => type_name == &self.name && self.doms.contains(&None), + } + } +} + +impl From<&CascadeString> for AssociatedResource { + fn from(cs: &CascadeString) -> Self { + let mut ranges = BTreeMap::new(); + // If the range is None, we just don't store it and later map lookups will return None, + // which is exactly what we want + if let Some(range) = cs.get_range() { + ranges.insert(cs.to_string(), range); + } + + match cs.as_ref().split_once('.') { + Some((dom, res)) => AssociatedResource { + name: res.into(), + doms: [Some(dom.into())].into(), + ranges, + }, + None => AssociatedResource { + name: cs.clone(), + doms: [None].into(), + ranges, + }, + } + } +} + +impl From for AssociatedResource { + fn from(cs: CascadeString) -> Self { + (&cs).into() + } +} + +impl PartialOrd for AssociatedResource { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for AssociatedResource { + fn cmp(&self, other: &Self) -> Ordering { + self.name.cmp(&other.name) + } +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct Associated { + pub resources: BTreeSet, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub enum InsertExtendTiming { + All, + Early, + Late, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub enum AnnotationInfo { + MakeList, + Associate(Associated), + NestAssociate(Associated), + Alias(CascadeString), + // Inherit isn't exposed to users, who should use the "inherits type" syntax, but its helpful + // internally to track inherits on extends as annotations + Inherit(Vec), + Derive(Vec), + NoDerive, +} + +impl AnnotationInfo { + // All data should exactly break into three sets: a.difference(b), b.difference(a) and + // a.intersection(b) (which is equivalent to b.intersection(a)) + + // Returns a single AnnotationInfo containing any overlap, if it exists + pub fn intersection(&self, other: &AnnotationInfo) -> Option { + use AnnotationInfo::*; + match (self, other) { + (MakeList, MakeList) => Some(AnnotationInfo::MakeList), + (NoDerive, NoDerive) => Some(AnnotationInfo::NoDerive), + (Associate(left), Associate(right)) | (NestAssociate(left), NestAssociate(right)) => { + let mut intersect: BTreeSet = BTreeSet::new(); + for l_res in &left.resources { + for r_res in &right.resources { + if l_res.name == r_res.name { + // TODO: The whole below should probably be in an impl in + // AssociatedResource. That allows at least ranges to become private + let mut unioned_ranges = BTreeMap::new(); + for (key, val) in &l_res.ranges { + if r_res.ranges.contains_key(key as &String) { + // TODO: I think this could result in weird error messages. + // We're just keeping the left and discarding the right. I'm + // not 100% sure how much that matters, but if there's + // something wrong with right and not left, the error would be + // confusing. Probably the common case is just "there is a + // parent named this", and so it doesn't overly matter if we + // point at right or left... + unioned_ranges.insert(key.to_string(), val.clone()); + } + } + // TODO: Do we need to worry about insert failing? + intersect.insert(AssociatedResource { + name: l_res.name.clone(), + doms: l_res.doms.union(&r_res.doms).cloned().collect(), + ranges: unioned_ranges, + }); + } + } + } + if intersect.is_empty() { + None + } else { + match self { + Associate(_) => Some(Associate(Associated { + resources: intersect, + })), + NestAssociate(_) => Some(NestAssociate(Associated { + resources: intersect, + })), + _ => { + // impossible + None + } + } + } + } + (Alias(left), Alias(right)) => { + if left == right { + Some(Alias(left.clone())) + } else { + None + } + } + // Treat all @derives as unique, because they require special processing later + (Derive(_), Derive(_)) => None, + // These should be filtered earlier and never processed here + (Inherit(_), Inherit(_)) => None, + // Enumerate the non-equal cases explicitly so that we get non-exhaustive match errors + // when updating the enum + (MakeList, _) + | (Associate(_), _) + | (NestAssociate(_), _) + | (Alias(_), _) + | (Inherit(_), _) + | (Derive(_), _) + | (NoDerive, _) => None, + } + } + + // Returns an AnnotationInfo with only the portion in self but not other. + pub fn difference(&self, other: &AnnotationInfo) -> Option { + use AnnotationInfo::*; + match (self, other) { + (MakeList, MakeList) => None, + (NoDerive, NoDerive) => None, + (Associate(left), Associate(right)) | (NestAssociate(left), NestAssociate(right)) => { + let difference: BTreeSet = left + .resources + .iter() + .filter(|l_res| !right.resources.iter().any(|r_res| r_res.name == l_res.name)) + .cloned() + .collect(); + + if difference.is_empty() { + None + } else { + match self { + Associate(_) => Some(Associate(Associated { + resources: difference, + })), + NestAssociate(_) => Some(NestAssociate(Associated { + resources: difference, + })), + _ => { + //impossible + None + } + } + } + } + (Alias(left), Alias(right)) => { + if left == right { + None + } else { + Some(Alias(left.clone())) + } + } + // No need to special handle Derive/Derive. Derives are always considered disjoint + (Derive(_), _) + | (MakeList, _) + | (Associate(_), _) + | (NestAssociate(_), _) + | (Alias(_), _) + | (NoDerive, _) + | (Inherit(_), _) => Some(self.clone()), + } + } + + pub fn insert_timing(&self) -> InsertExtendTiming { + match self { + AnnotationInfo::Associate(_) => InsertExtendTiming::All, + AnnotationInfo::NestAssociate(_) => InsertExtendTiming::Early, + // Inherit is Early, but note that it may also be set on an associated resource, in + // which case it also has special handling in create_synthetic resource. The "Early" + // handling handles regular types + AnnotationInfo::Inherit(_) => InsertExtendTiming::Early, + AnnotationInfo::Derive(_) => InsertExtendTiming::Late, + AnnotationInfo::NoDerive => InsertExtendTiming::Late, + AnnotationInfo::MakeList => InsertExtendTiming::Late, + AnnotationInfo::Alias(_) => InsertExtendTiming::Late, + } + } + + pub fn as_inherit(&self) -> Option<&Vec> { + if let AnnotationInfo::Inherit(v) = self { + Some(v) + } else { + None + } + } +} + +pub trait Annotated { + fn get_annotations(&self) -> std::collections::btree_set::Iter; +} + +fn get_associate( + file: &SimpleFile, + annotation_name_range: Option>, + annotation: &Annotation, +) -> Result { + let mut args = annotation.arguments.iter(); + + let res_list = match args.next() { + None => { + return Err(ErrorItem::make_compile_or_internal_error( + "Missing resource list as first argument", + Some(file), + annotation_name_range, + "You must use a set of resource names, enclosed by square brackets, as first argument.", + )); + } + Some(Argument::List(l)) => l, + Some(a) => { + return Err(ErrorItem::make_compile_or_internal_error( + "Invalid argument type", + Some(file), + a.get_range(), + "You must use a set of resource names, enclosed by square brackets, as first argument.", + )); + } + }; + + if let Some(a) = args.next() { + return Err(ErrorItem::make_compile_or_internal_error( + "Superfluous argument", + Some(file), + a.get_range(), + "There must be only one argument.", + )); + } + + Ok(AnnotationInfo::Associate(Associated { + // Checks for duplicate resources. + resources: res_list.iter().try_fold(BTreeSet::new(), |mut s, e| { + if !s.insert(e.into()) { + Err(ErrorItem::make_compile_or_internal_error( + "Duplicate resource", + Some(file), + e.get_range(), + "Only unique resource names are valid.", + )) + } else { + Ok(s) + } + })?, + })) +} + +pub fn get_type_annotations( + file: &SimpleFile, + annotations: &Annotations, +) -> Result>, ErrorItem> { + let mut infos = BTreeSet::new(); + let mut warnings = Warnings::new(); + + // Only allow a set of specific annotation names and strictly check their arguments. + // TODO: Add tests to verify these checks. + for annotation in annotations.annotations.iter() { + match annotation.name.as_ref() { + "makelist" => { + // TODO: Check arguments + // Multiple @makelist annotations doesn't make sense. + if !infos.insert(AnnotationInfo::MakeList) { + return Err(ErrorItem::make_compile_or_internal_error( + "Multiple @makelist annotations", + Some(file), + annotation.name.get_range(), + "You need to remove duplicated @makelist annotations.", + )); + } + } + "associate" => { + // Multiple @associate annotations doesn't make sense. + if !infos.insert(get_associate( + file, + annotation.name.get_range(), + annotation, + )?) { + return Err(ErrorItem::make_compile_or_internal_error( + "Multiple @associate annotations", + Some(file), + annotation.name.get_range(), + "You need to remove duplicated @associate annotations.", + )); + } + } + "alias" => { + for a in &annotation.arguments { + match a { + Argument::Var(a) => { + infos.insert(AnnotationInfo::Alias(a.clone())); + } + _ => { + return Err(ErrorItem::make_compile_or_internal_error( + "Invalid alias", + Some(file), + a.get_range(), + "This must be a symbol", + )); + } + } + } + } + "derive" => { + // Arguments are validated at function creation time + infos.insert(AnnotationInfo::Derive(annotation.arguments.clone())); + } + "noderive" => { + // Do not implicit derive for this type + infos.insert(AnnotationInfo::NoDerive); + } + "hint" => { + // If get_range() is none, we generated a synthetic hint. This could be because of + // inheritance, in which case there was a warning on the parent. Otherwise, if we + // generated a synthetic hint for some reason, we can always generate it + // differently if the signature changes, and the point of the warning is to not + // rely on any existing signature. So a warning is only necessary if the hint is + // actually in source. + if let Some(range) = annotation.name.get_range() { + warnings.push(Warning::new("The hint annotation is not yet supported", + file, + range, + "The signature expected by this annotation may change without warning, and it is currently not functional.")); + } + } + _ => { + return Err(ErrorItem::make_compile_or_internal_error( + "Unknown annotation", + Some(file), + annotation.name.get_range(), + "This is not a valid annotation name.", + )); + } + } + } + Ok(WithWarnings::new(infos, warnings)) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn ar_string_is_instance_test() { + let foo_bar = CascadeString::from("foo.bar"); + let bar = CascadeString::from("bar"); + let foo = CascadeString::from("foo"); + let ar = AssociatedResource::from(&foo_bar); + + assert!(ar.string_is_instance(&foo_bar)); + assert!(!ar.string_is_instance(&bar)); + assert!(!ar.string_is_instance(&foo)); + } +} diff --git a/src/compile.rs b/src/compile.rs index 99bbf65..6bc708b 100644 --- a/src/compile.rs +++ b/src/compile.rs @@ -8,6 +8,9 @@ use std::convert::TryFrom; use std::ops::Range; use crate::alias_map::Declared; +use crate::annotations::{ + get_type_annotations, Annotated, AnnotationInfo, Associated, InsertExtendTiming, +}; use crate::ast::{ Annotation, Annotations, Argument, CascadeString, Declaration, Expression, FuncCall, LetBinding, Machine, Module, PolicyFile, Statement, TypeDecl, @@ -24,9 +27,8 @@ use crate::functions::{ FunctionMap, ValidatedCall, ValidatedStatement, }; use crate::internal_rep::{ - generate_sid_rules, get_type_annotations, validate_derive_args, Annotated, AnnotationInfo, - Associated, ClassList, Context, InsertExtendTiming, Sid, TypeInfo, TypeInstance, TypeMap, - TypeVar, + generate_sid_rules, validate_derive_args, ClassList, Context, Sid, TypeInfo, TypeInstance, + TypeMap, TypeVar, }; use crate::machine::{MachineMap, ModuleMap, ValidatedMachine, ValidatedModule}; use crate::warning::{Warnings, WithWarnings}; diff --git a/src/functions.rs b/src/functions.rs index bfd8a66..e0eea6f 100644 --- a/src/functions.rs +++ b/src/functions.rs @@ -13,6 +13,7 @@ use std::str::FromStr; use codespan_reporting::files::SimpleFile; use crate::alias_map::{AliasMap, Declared}; +use crate::annotations::{Annotated, AnnotationInfo}; use crate::ast::{ get_all_func_calls, get_cil_name, Annotation, Argument, BuiltIns, CascadeString, DeclaredArgument, FuncCall, FuncDecl, IpAddr, Port, Statement, @@ -23,8 +24,8 @@ use crate::error::{ add_or_create_compile_error, CascadeErrors, CompileError, ErrorItem, InternalError, }; use crate::internal_rep::{ - type_name_from_string, typeinfo_from_string, Annotated, AnnotationInfo, ClassList, Context, - Sid, TypeInfo, TypeInstance, TypeMap, + type_name_from_string, typeinfo_from_string, ClassList, Context, Sid, TypeInfo, TypeInstance, + TypeMap, }; use crate::obj_class::perm_list_to_sexp; use crate::warning::{Warning, Warnings, WithWarnings}; diff --git a/src/internal_rep.rs b/src/internal_rep.rs index e84e50b..007aaba 100644 --- a/src/internal_rep.rs +++ b/src/internal_rep.rs @@ -12,17 +12,15 @@ use std::ops::Range; use codespan_reporting::files::SimpleFile; use crate::alias_map::{AliasMap, Declared}; -use crate::ast::{ - Annotation, Annotations, Argument, CascadeString, DeclaredArgument, FuncCall, IpAddr, Port, - TypeDecl, -}; +use crate::annotations::{get_type_annotations, Annotated, AnnotationInfo, AssociatedResource}; +use crate::ast::{Argument, CascadeString, DeclaredArgument, FuncCall, IpAddr, Port, TypeDecl}; use crate::constants; use crate::context::{BlockType, Context as BlockContext}; use crate::error::{CascadeErrors, ErrorItem, InternalError}; use crate::functions::{ validate_arguments, ArgForValidation, FunctionArgument, FunctionClass, FunctionMap, }; -use crate::warning::{Warning, Warnings, WithWarnings}; +use crate::warning::{Warnings, WithWarnings}; const DEFAULT_USER: &str = "system_u"; const DEFAULT_OBJECT_ROLE: &str = "object_r"; @@ -31,276 +29,6 @@ const DEFAULT_MLS: &str = "s0"; pub type TypeMap = AliasMap; -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct AssociatedResource { - name: CascadeString, - doms: BTreeSet>, - ranges: BTreeMap>, -} - -impl AssociatedResource { - // Unlike most get_range() functions, this one takes an argument. An AssociatedResource - // possibly contains information about various associated points, so we need to know the name - // of the resource we want the range for - pub fn get_range(&self, resource_name: &str) -> Option> { - self.ranges.get(resource_name).cloned() - } - - pub fn name(&self) -> &CascadeString { - &self.name - } - - pub fn get_class_names(&self) -> Vec { - self.doms - .iter() - .map(|d| match d { - Some(d) => { - format!("{}.{}", d, &self.name) - } - None => self.name.to_string(), - }) - .collect() - } - - pub fn basename(&self) -> &str { - self.name.as_ref() - } - - // Return true if type_name is one of the resources that have been combined in this - // AssociatedResource - pub fn string_is_instance(&self, type_name: &CascadeString) -> bool { - match type_name.as_ref().split_once('.') { - Some((dom, res)) => { - res == self.name && self.doms.contains(&Some(CascadeString::from(dom))) - } - None => type_name == &self.name && self.doms.contains(&None), - } - } -} - -impl From<&CascadeString> for AssociatedResource { - fn from(cs: &CascadeString) -> Self { - let mut ranges = BTreeMap::new(); - // If the range is None, we just don't store it and later map lookups will return None, - // which is exactly what we want - if let Some(range) = cs.get_range() { - ranges.insert(cs.to_string(), range); - } - - match cs.as_ref().split_once('.') { - Some((dom, res)) => AssociatedResource { - name: res.into(), - doms: [Some(dom.into())].into(), - ranges, - }, - None => AssociatedResource { - name: cs.clone(), - doms: [None].into(), - ranges, - }, - } - } -} - -impl From for AssociatedResource { - fn from(cs: CascadeString) -> Self { - (&cs).into() - } -} - -impl PartialOrd for AssociatedResource { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for AssociatedResource { - fn cmp(&self, other: &Self) -> Ordering { - self.name.cmp(&other.name) - } -} - -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct Associated { - pub resources: BTreeSet, -} - -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub enum InsertExtendTiming { - All, - Early, - Late, -} - -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub enum AnnotationInfo { - MakeList, - Associate(Associated), - NestAssociate(Associated), - Alias(CascadeString), - // Inherit isn't exposed to users, who should use the "inherits type" syntax, but its helpful - // internally to track inherits on extends as annotations - Inherit(Vec), - Derive(Vec), - NoDerive, -} - -impl AnnotationInfo { - // All data should exactly break into three sets: a.difference(b), b.difference(a) and - // a.intersection(b) (which is equivalent to b.intersection(a)) - - // Returns a single AnnotationInfo containing any overlap, if it exists - pub fn intersection(&self, other: &AnnotationInfo) -> Option { - use AnnotationInfo::*; - match (self, other) { - (MakeList, MakeList) => Some(AnnotationInfo::MakeList), - (NoDerive, NoDerive) => Some(AnnotationInfo::NoDerive), - (Associate(left), Associate(right)) | (NestAssociate(left), NestAssociate(right)) => { - let mut intersect: BTreeSet = BTreeSet::new(); - for l_res in &left.resources { - for r_res in &right.resources { - if l_res.name == r_res.name { - // TODO: The whole below should probably be in an impl in - // AssociatedResource. That allows at least ranges to become private - let mut unioned_ranges = BTreeMap::new(); - for (key, val) in &l_res.ranges { - if r_res.ranges.contains_key(key as &String) { - // TODO: I think this could result in weird error messages. - // We're just keeping the left and discarding the right. I'm - // not 100% sure how much that matters, but if there's - // something wrong with right and not left, the error would be - // confusing. Probably the common case is just "there is a - // parent named this", and so it doesn't overly matter if we - // point at right or left... - unioned_ranges.insert(key.to_string(), val.clone()); - } - } - // TODO: Do we need to worry about insert failing? - intersect.insert(AssociatedResource { - name: l_res.name.clone(), - doms: l_res.doms.union(&r_res.doms).cloned().collect(), - ranges: unioned_ranges, - }); - } - } - } - if intersect.is_empty() { - None - } else { - match self { - Associate(_) => Some(Associate(Associated { - resources: intersect, - })), - NestAssociate(_) => Some(NestAssociate(Associated { - resources: intersect, - })), - _ => { - // impossible - None - } - } - } - } - (Alias(left), Alias(right)) => { - if left == right { - Some(Alias(left.clone())) - } else { - None - } - } - // Treat all @derives as unique, because they require special processing later - (Derive(_), Derive(_)) => None, - // These should be filtered earlier and never processed here - (Inherit(_), Inherit(_)) => None, - // Enumerate the non-equal cases explicitly so that we get non-exhaustive match errors - // when updating the enum - (MakeList, _) - | (Associate(_), _) - | (NestAssociate(_), _) - | (Alias(_), _) - | (Inherit(_), _) - | (Derive(_), _) - | (NoDerive, _) => None, - } - } - - // Returns an AnnotationInfo with only the portion in self but not other. - pub fn difference(&self, other: &AnnotationInfo) -> Option { - use AnnotationInfo::*; - match (self, other) { - (MakeList, MakeList) => None, - (NoDerive, NoDerive) => None, - (Associate(left), Associate(right)) | (NestAssociate(left), NestAssociate(right)) => { - let difference: BTreeSet = left - .resources - .iter() - .filter(|l_res| !right.resources.iter().any(|r_res| r_res.name == l_res.name)) - .cloned() - .collect(); - - if difference.is_empty() { - None - } else { - match self { - Associate(_) => Some(Associate(Associated { - resources: difference, - })), - NestAssociate(_) => Some(NestAssociate(Associated { - resources: difference, - })), - _ => { - //impossible - None - } - } - } - } - (Alias(left), Alias(right)) => { - if left == right { - None - } else { - Some(Alias(left.clone())) - } - } - // No need to special handle Derive/Derive. Derives are always considered disjoint - (Derive(_), _) - | (MakeList, _) - | (Associate(_), _) - | (NestAssociate(_), _) - | (Alias(_), _) - | (NoDerive, _) - | (Inherit(_), _) => Some(self.clone()), - } - } - - pub fn insert_timing(&self) -> InsertExtendTiming { - match self { - AnnotationInfo::Associate(_) => InsertExtendTiming::All, - AnnotationInfo::NestAssociate(_) => InsertExtendTiming::Early, - // Inherit is Early, but note that it may also be set on an associated resource, in - // which case it also has special handling in create_synthetic resource. The "Early" - // handling handles regular types - AnnotationInfo::Inherit(_) => InsertExtendTiming::Early, - AnnotationInfo::Derive(_) => InsertExtendTiming::Late, - AnnotationInfo::NoDerive => InsertExtendTiming::Late, - AnnotationInfo::MakeList => InsertExtendTiming::Late, - AnnotationInfo::Alias(_) => InsertExtendTiming::Late, - } - } - - pub fn as_inherit(&self) -> Option<&Vec> { - if let AnnotationInfo::Inherit(v) = self { - Some(v) - } else { - None - } - } -} - -pub trait Annotated { - fn get_annotations(&self) -> std::collections::btree_set::Iter; -} - // TODO: This is only pub because compile.rs hardcodes sids right now. Once those are removed, // make this private #[derive(Clone, Debug, Copy, PartialEq, Eq)] @@ -738,149 +466,6 @@ pub fn type_slice_to_variant<'a, 'b>( } } -fn get_associate( - file: &SimpleFile, - annotation_name_range: Option>, - annotation: &Annotation, -) -> Result { - let mut args = annotation.arguments.iter(); - - let res_list = match args.next() { - None => { - return Err(ErrorItem::make_compile_or_internal_error( - "Missing resource list as first argument", - Some(file), - annotation_name_range, - "You must use a set of resource names, enclosed by square brackets, as first argument.", - )); - } - Some(Argument::List(l)) => l, - Some(a) => { - return Err(ErrorItem::make_compile_or_internal_error( - "Invalid argument type", - Some(file), - a.get_range(), - "You must use a set of resource names, enclosed by square brackets, as first argument.", - )); - } - }; - - if let Some(a) = args.next() { - return Err(ErrorItem::make_compile_or_internal_error( - "Superfluous argument", - Some(file), - a.get_range(), - "There must be only one argument.", - )); - } - - Ok(AnnotationInfo::Associate(Associated { - // Checks for duplicate resources. - resources: res_list.iter().try_fold(BTreeSet::new(), |mut s, e| { - if !s.insert(e.into()) { - Err(ErrorItem::make_compile_or_internal_error( - "Duplicate resource", - Some(file), - e.get_range(), - "Only unique resource names are valid.", - )) - } else { - Ok(s) - } - })?, - })) -} - -pub fn get_type_annotations( - file: &SimpleFile, - annotations: &Annotations, -) -> Result>, ErrorItem> { - let mut infos = BTreeSet::new(); - let mut warnings = Warnings::new(); - - // Only allow a set of specific annotation names and strictly check their arguments. - // TODO: Add tests to verify these checks. - for annotation in annotations.annotations.iter() { - match annotation.name.as_ref() { - "makelist" => { - // TODO: Check arguments - // Multiple @makelist annotations doesn't make sense. - if !infos.insert(AnnotationInfo::MakeList) { - return Err(ErrorItem::make_compile_or_internal_error( - "Multiple @makelist annotations", - Some(file), - annotation.name.get_range(), - "You need to remove duplicated @makelist annotations.", - )); - } - } - "associate" => { - // Multiple @associate annotations doesn't make sense. - if !infos.insert(get_associate( - file, - annotation.name.get_range(), - annotation, - )?) { - return Err(ErrorItem::make_compile_or_internal_error( - "Multiple @associate annotations", - Some(file), - annotation.name.get_range(), - "You need to remove duplicated @associate annotations.", - )); - } - } - "alias" => { - for a in &annotation.arguments { - match a { - Argument::Var(a) => { - infos.insert(AnnotationInfo::Alias(a.clone())); - } - _ => { - return Err(ErrorItem::make_compile_or_internal_error( - "Invalid alias", - Some(file), - a.get_range(), - "This must be a symbol", - )); - } - } - } - } - "derive" => { - // Arguments are validated at function creation time - infos.insert(AnnotationInfo::Derive(annotation.arguments.clone())); - } - "noderive" => { - // Do not implicit derive for this type - infos.insert(AnnotationInfo::NoDerive); - } - "hint" => { - // If get_range() is none, we generated a synthetic hint. This could be because of - // inheritance, in which case there was a warning on the parent. Otherwise, if we - // generated a synthetic hint for some reason, we can always generate it - // differently if the signature changes, and the point of the warning is to not - // rely on any existing signature. So a warning is only necessary if the hint is - // actually in source. - if let Some(range) = annotation.name.get_range() { - warnings.push(Warning::new("The hint annotation is not yet supported", - file, - range, - "The signature expected by this annotation may change without warning, and it is currently not functional.")); - } - } - _ => { - return Err(ErrorItem::make_compile_or_internal_error( - "Unknown annotation", - Some(file), - annotation.name.get_range(), - "This is not a valid annotation name.", - )); - } - } - } - Ok(WithWarnings::new(infos, warnings)) -} - // On success, returns a tuple of parents to derive from and the names of the functions to derive pub fn validate_derive_args<'a>( target_type: &'a TypeInfo, @@ -1631,18 +1216,6 @@ impl<'a> From<&'a TypeInfo> for TypeInstance<'a> { mod tests { use super::*; - #[test] - fn ar_string_is_instance_test() { - let foo_bar = CascadeString::from("foo.bar"); - let bar = CascadeString::from("bar"); - let foo = CascadeString::from("foo"); - let ar = AssociatedResource::from(&foo_bar); - - assert!(ar.string_is_instance(&foo_bar)); - assert!(!ar.string_is_instance(&bar)); - assert!(!ar.string_is_instance(&foo)); - } - #[test] fn basename_test() { let ti = TypeInfo { diff --git a/src/lib.rs b/src/lib.rs index 12fd122..e554989 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,6 +8,7 @@ extern crate lalrpop_util; extern crate thiserror; mod alias_map; +mod annotations; mod ast; mod compile; mod constants; @@ -27,11 +28,11 @@ mod test; use std::collections::{BTreeMap, BTreeSet, HashMap}; +use crate::annotations::InsertExtendTiming; use crate::ast::{Argument, CascadeString, Declaration, Expression, Policy, PolicyFile}; use crate::context::{BlockType, Context}; use crate::error::{CascadeErrors, InternalError, InvalidMachineError, ParseErrorMsg}; use crate::functions::{FunctionClass, FunctionMap}; -use crate::internal_rep::InsertExtendTiming; use crate::machine::{MachineMap, ModuleMap, ValidatedMachine, ValidatedModule}; use crate::util::append_set_map; pub use crate::warning::Warnings; diff --git a/src/machine.rs b/src/machine.rs index bc84788..87df824 100644 --- a/src/machine.rs +++ b/src/machine.rs @@ -8,9 +8,10 @@ use std::ops::Range; use codespan_reporting::files::SimpleFile; use crate::alias_map::{AliasMap, Declared}; +use crate::annotations::{Annotated, AnnotationInfo}; use crate::ast::{Annotations, Argument, CascadeString, Module}; use crate::error::{CascadeErrors, ErrorItem}; -use crate::internal_rep::{Annotated, AnnotationInfo, TypeInfo}; +use crate::internal_rep::TypeInfo; pub type ModuleMap<'a> = AliasMap>;