diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d73c19f..40c5ae31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,16 @@ Rhai Release Notes Version 0.20.2 ============== +This version adds a number of convenience features: + +* Ability for a `Dynamic` to hold an `i32` _tag_ of arbitrary data + +* Simplifies dynamic properties access by falling back to an indexer (passing the name of the property as a string) when a property is not found. + Bug fixes --------- -* Constant propagation during optimization for constants held in a custom scope now works properly instead of always replacing by `()`. +* Propagation of constants held in a custom scope now works properly instead of always replacing by `()`. Breaking changes ---------------- @@ -15,14 +21,17 @@ Breaking changes * `Engine::disable_doc_comments` is removed because doc-comments are now placed under the `metadata` feature flag. * Registering a custom syntax now only requires specifying whether the `Scope` is adjusted (i.e. whether variables are added or removed). There is no need to specify the number of variables added/removed. * Assigning to a property of a constant is now allowed and no longer raise an `EvalAltResult::ErrorAssignmentToConstant` error. This is to facilitate the Singleton pattern. Registered setter functions are automatically guarded against setters calling on constants and will continue to raise errors unless the `pure` attribute is present (for plugins). +* If a property getter/setter is not found, an indexer with string index, if any, is tried. +* The indexers API (`Engine::register_indexer_XXX` and `Module::set_indexer_XXX`) are now also exposed under `no_index`. New features ------------ -* Each `Dynamic` value can now contain arbitrary data (type `i16`) in the form of a _tag_. This is to use up otherwise wasted space in the `Dynamic` type. +* Each `Dynamic` value can now contain arbitrary data (type `i32`) in the form of a _tag_. This is to use up otherwise wasted space in the `Dynamic` type. * A new internal feature `no_smartstring` to turn off `SmartString` for those rare cases that it is needed. * `DynamicReadLock` and `DynamicWriteLoc` are exposed under `internals`. * `From>>` is added for `Dynamic` mapping directly to a shared value, together with support for `Dynamic::from`. +* An indexer with string index acts as a _fallback_ to a property getter/setter. Enhancements ------------ diff --git a/Cargo.toml b/Cargo.toml index dd2d32de..f2236a0b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -92,7 +92,7 @@ default_features = false optional = true [dependencies.rust_decimal] -version = "1.13" +version = "1.14" default_features = false features = ["maths"] optional = true diff --git a/scripts/fibonacci.rhai b/scripts/fibonacci.rhai index 0787a350..fd3ba5c0 100644 --- a/scripts/fibonacci.rhai +++ b/scripts/fibonacci.rhai @@ -6,7 +6,7 @@ const REPEAT = 5; fn fib(n) { if n < 2 { - n + n } else { fib(n-1) + fib(n-2) } @@ -19,7 +19,7 @@ let result; let now = timestamp(); for n in range(0, REPEAT) { - result = fib(TARGET); + result = fib(TARGET); } print(`Finished. Run time = ${now.elapsed} seconds.`); diff --git a/scripts/loop.rhai b/scripts/loop.rhai index 846450f0..707152c4 100644 --- a/scripts/loop.rhai +++ b/scripts/loop.rhai @@ -8,7 +8,7 @@ loop { x -= 1; - if x <= 0 { break; } + if x <= 0 { break; } } export x as foo; diff --git a/scripts/mat_mul.rhai b/scripts/mat_mul.rhai index f3f93724..cb0ced95 100644 --- a/scripts/mat_mul.rhai +++ b/scripts/mat_mul.rhai @@ -1,13 +1,13 @@ const SIZE = 50; fn new_mat(x, y) { - let row = []; - row.pad(y, 0.0); - - let matrix = []; - matrix.pad(x, row); - - matrix + let row = []; + row.pad(y, 0.0); + + let matrix = []; + matrix.pad(x, row); + + matrix } fn mat_gen() { @@ -20,13 +20,13 @@ fn mat_gen() { m[i][j] = tmp * (i - j) * (i + j); } } - + m } fn mat_mul(a, b) { let b2 = new_mat(a[0].len, b[0].len); - + for i in range(0, a[0].len) { for j in range(0, b[0].len) { b2[j][i] = b[i][j]; @@ -35,13 +35,13 @@ fn mat_mul(a, b) { let c = new_mat(a.len, b[0].len); - for i in range(0, c.len) { - for j in range(0, c[i].len) { - c[i][j] = 0.0; - - for z in range(0, a[i].len) { - c[i][j] += a[i][z] * b2[j][z]; - } + for i in range(0, c.len) { + for j in range(0, c[i].len) { + c[i][j] = 0.0; + + for z in range(0, a[i].len) { + c[i][j] += a[i][z] * b2[j][z]; + } } } @@ -56,7 +56,7 @@ const c = mat_mul(a, b); /* for i in range(0, SIZE) { - print(c[i]); + print(c[i]); } */ diff --git a/scripts/strings_map.rhai b/scripts/strings_map.rhai index da0dbd87..ec528ecf 100644 --- a/scripts/strings_map.rhai +++ b/scripts/strings_map.rhai @@ -5,79 +5,79 @@ let now = timestamp(); let adverbs = [ "moderately", "really", "slightly", "very" ]; let adjectives = [ - "abandoned", "able", "absolute", "academic", "acceptable", "acclaimed", - "accomplished", "accurate", "aching", "acidic", "acrobatic", "active", - "actual", "adept", "admirable", "admired", "adolescent", "adorable", "adored", - "advanced", "adventurous", "affectionate", "afraid", "aged", "aggravating", - "aggressive", "agile", "agitated", "agonizing", "agreeable", "ajar", - "alarmed", "alarming", "alert", "alienated", "alive", "all", "altruistic", - "amazing", "ambitious", "ample", "amused", "amusing", "anchored", "ancient", - "angelic", "angry", "anguished", "animated", "annual", "another", "antique", - "anxious", "any", "apprehensive", "appropriate", "apt", "arctic", "arid", - "aromatic", "artistic", "ashamed", "assured", "astonishing", "athletic", - "attached", "attentive", "attractive", "austere", "authentic", "authorized", - "automatic", "avaricious", "average", "aware", "awesome", "awful", "awkward", - "babyish", "back", "bad", "baggy", "bare", "barren", "basic", "beautiful", - "belated", "beloved", "beneficial", "best", "better", "bewitched", "big", - "big-hearted", "biodegradable", "bite-sized", "bitter", "black", - "black-and-white", "bland", "blank", "blaring", "bleak", "blind", "blissful", - "blond", "blue", "blushing", "bogus", "boiling", "bold", "bony", "boring", - "bossy", "both", "bouncy", "bountiful", "bowed", "brave", "breakable", - "brief", "bright", "brilliant", "brisk", "broken", "bronze", "brown", - "bruised", "bubbly", "bulky", "bumpy", "buoyant", "burdensome", "burly", - "bustling", "busy", "buttery", "buzzing", "calculating", "calm", "candid", - "canine", "capital", "carefree", "careful", "careless", "caring", "cautious", - "cavernous", "celebrated", "charming", "cheap", "cheerful", "cheery", "chief", - "chilly", "chubby", "circular", "classic", "clean", "clear", "clear-cut", - "clever", "close", "closed", "cloudy", "clueless", "clumsy", "cluttered", - "coarse", "cold", "colorful", "colorless", "colossal", "comfortable", - "common", "compassionate", "competent", "complete", "complex", "complicated", - "composed", "concerned", "concrete", "confused", "conscious", "considerate", - "constant", "content", "conventional", "cooked", "cool", "cooperative", - "coordinated", "corny", "corrupt", "costly", "courageous", "courteous", - "crafty" + "abandoned", "able", "absolute", "academic", "acceptable", "acclaimed", + "accomplished", "accurate", "aching", "acidic", "acrobatic", "active", + "actual", "adept", "admirable", "admired", "adolescent", "adorable", "adored", + "advanced", "adventurous", "affectionate", "afraid", "aged", "aggravating", + "aggressive", "agile", "agitated", "agonizing", "agreeable", "ajar", + "alarmed", "alarming", "alert", "alienated", "alive", "all", "altruistic", + "amazing", "ambitious", "ample", "amused", "amusing", "anchored", "ancient", + "angelic", "angry", "anguished", "animated", "annual", "another", "antique", + "anxious", "any", "apprehensive", "appropriate", "apt", "arctic", "arid", + "aromatic", "artistic", "ashamed", "assured", "astonishing", "athletic", + "attached", "attentive", "attractive", "austere", "authentic", "authorized", + "automatic", "avaricious", "average", "aware", "awesome", "awful", "awkward", + "babyish", "back", "bad", "baggy", "bare", "barren", "basic", "beautiful", + "belated", "beloved", "beneficial", "best", "better", "bewitched", "big", + "big-hearted", "biodegradable", "bite-sized", "bitter", "black", + "black-and-white", "bland", "blank", "blaring", "bleak", "blind", "blissful", + "blond", "blue", "blushing", "bogus", "boiling", "bold", "bony", "boring", + "bossy", "both", "bouncy", "bountiful", "bowed", "brave", "breakable", + "brief", "bright", "brilliant", "brisk", "broken", "bronze", "brown", + "bruised", "bubbly", "bulky", "bumpy", "buoyant", "burdensome", "burly", + "bustling", "busy", "buttery", "buzzing", "calculating", "calm", "candid", + "canine", "capital", "carefree", "careful", "careless", "caring", "cautious", + "cavernous", "celebrated", "charming", "cheap", "cheerful", "cheery", "chief", + "chilly", "chubby", "circular", "classic", "clean", "clear", "clear-cut", + "clever", "close", "closed", "cloudy", "clueless", "clumsy", "cluttered", + "coarse", "cold", "colorful", "colorless", "colossal", "comfortable", + "common", "compassionate", "competent", "complete", "complex", "complicated", + "composed", "concerned", "concrete", "confused", "conscious", "considerate", + "constant", "content", "conventional", "cooked", "cool", "cooperative", + "coordinated", "corny", "corrupt", "costly", "courageous", "courteous", + "crafty" ]; let animals = [ - "aardvark", "african buffalo", "albatross", "alligator", "alpaca", "ant", - "anteater", "antelope", "ape", "armadillo", "baboon", "badger", "barracuda", - "bat", "bear", "beaver", "bee", "bison", "black panther", "blue jay", "boar", - "butterfly", "camel", "capybara", "carduelis", "caribou", "cassowary", "cat", - "caterpillar", "cattle", "chamois", "cheetah", "chicken", "chimpanzee", - "chinchilla", "chough", "clam", "cobra", "cockroach", "cod", "cormorant", - "coyote", "crab", "crane", "crocodile", "crow", "curlew", "deer", "dinosaur", - "dog", "dolphin", "domestic pig", "donkey", "dotterel", "dove", "dragonfly", - "duck", "dugong", "dunlin", "eagle", "echidna", "eel", "elephant seal", - "elephant", "elk", "emu", "falcon", "ferret", "finch", "fish", "flamingo", - "fly", "fox", "frog", "gaur", "gazelle", "gerbil", "giant panda", "giraffe", - "gnat", "goat", "goldfish", "goose", "gorilla", "goshawk", "grasshopper", - "grouse", "guanaco", "guinea fowl", "guinea pig", "gull", "hamster", "hare", - "hawk", "hedgehog", "heron", "herring", "hippopotamus", "hornet", "horse", - "human", "hummingbird", "hyena", "ibex", "ibis", "jackal", "jaguar", "jay", - "jellyfish", "kangaroo", "kingfisher", "koala", "komodo dragon", "kookabura", - "kouprey", "kudu", "lapwing", "lark", "lemur", "leopard", "lion", "llama", - "lobster", "locust", "loris", "louse", "lyrebird", "magpie", "mallard", - "manatee", "mandrill", "mantis", "marten", "meerkat", "mink", "mole", - "mongoose", "monkey", "moose", "mosquito", "mouse", "mule", "narwhal", "newt", - "nightingale", "octopus", "okapi", "opossum", "oryx", "ostrich", "otter", - "owl", "oyster", "parrot", "partridge", "peafowl", "pelican", "penguin", - "pheasant", "pigeon", "pinniped", "polar bear", "pony", "porcupine", - "porpoise", "prairie dog", "quail", "quelea", "quetzal", "rabbit", "raccoon", - "ram", "rat", "raven", "red deer", "red panda", "reindeer", "rhinoceros", - "rook", "salamander", "salmon", "sand dollar", "sandpiper", "sardine", - "scorpion", "sea lion", "sea urchin", "seahorse", "shark", "sheep", "shrew", - "skunk", "snail", "snake", "sparrow", "spider", "spoonbill", "squid", - "wallaby", "wildebeest" + "aardvark", "african buffalo", "albatross", "alligator", "alpaca", "ant", + "anteater", "antelope", "ape", "armadillo", "baboon", "badger", "barracuda", + "bat", "bear", "beaver", "bee", "bison", "black panther", "blue jay", "boar", + "butterfly", "camel", "capybara", "carduelis", "caribou", "cassowary", "cat", + "caterpillar", "cattle", "chamois", "cheetah", "chicken", "chimpanzee", + "chinchilla", "chough", "clam", "cobra", "cockroach", "cod", "cormorant", + "coyote", "crab", "crane", "crocodile", "crow", "curlew", "deer", "dinosaur", + "dog", "dolphin", "domestic pig", "donkey", "dotterel", "dove", "dragonfly", + "duck", "dugong", "dunlin", "eagle", "echidna", "eel", "elephant seal", + "elephant", "elk", "emu", "falcon", "ferret", "finch", "fish", "flamingo", + "fly", "fox", "frog", "gaur", "gazelle", "gerbil", "giant panda", "giraffe", + "gnat", "goat", "goldfish", "goose", "gorilla", "goshawk", "grasshopper", + "grouse", "guanaco", "guinea fowl", "guinea pig", "gull", "hamster", "hare", + "hawk", "hedgehog", "heron", "herring", "hippopotamus", "hornet", "horse", + "human", "hummingbird", "hyena", "ibex", "ibis", "jackal", "jaguar", "jay", + "jellyfish", "kangaroo", "kingfisher", "koala", "komodo dragon", "kookabura", + "kouprey", "kudu", "lapwing", "lark", "lemur", "leopard", "lion", "llama", + "lobster", "locust", "loris", "louse", "lyrebird", "magpie", "mallard", + "manatee", "mandrill", "mantis", "marten", "meerkat", "mink", "mole", + "mongoose", "monkey", "moose", "mosquito", "mouse", "mule", "narwhal", "newt", + "nightingale", "octopus", "okapi", "opossum", "oryx", "ostrich", "otter", + "owl", "oyster", "parrot", "partridge", "peafowl", "pelican", "penguin", + "pheasant", "pigeon", "pinniped", "polar bear", "pony", "porcupine", + "porpoise", "prairie dog", "quail", "quelea", "quetzal", "rabbit", "raccoon", + "ram", "rat", "raven", "red deer", "red panda", "reindeer", "rhinoceros", + "rook", "salamander", "salmon", "sand dollar", "sandpiper", "sardine", + "scorpion", "sea lion", "sea urchin", "seahorse", "shark", "sheep", "shrew", + "skunk", "snail", "snake", "sparrow", "spider", "spoonbill", "squid", + "wallaby", "wildebeest" ]; let keys = []; for animal in animals { - for adjective in adjectives { - for adverb in adverbs { - keys.push(`${adverb} ${adjective} ${animal}`) + for adjective in adjectives { + for adverb in adverbs { + keys.push(`${adverb} ${adjective} ${animal}`) + } } - } } let map = #{}; @@ -85,18 +85,18 @@ let map = #{}; let i = 0; for key in keys { - map[key] = i; - i += 1; + map[key] = i; + i += 1; } let sum = 0; for key in keys { - sum += map[key]; + sum += map[key]; } for key in keys { - map.remove(key); + map.remove(key); } print(`Sum = ${sum}`); diff --git a/src/ast.rs b/src/ast.rs index 21841502..3fd9f6fd 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -13,7 +13,6 @@ use std::{ collections::BTreeMap, fmt, hash::Hash, - iter::empty, mem, num::{NonZeroU8, NonZeroUsize}, ops::{Add, AddAssign, Deref, DerefMut}, @@ -254,12 +253,11 @@ impl AST { /// Set the source. #[inline(always)] pub fn set_source(&mut self, source: impl Into) -> &mut Self { - self.source = Some(source.into()); - - if let Some(module) = Shared::get_mut(&mut self.functions) { - module.set_id(self.source.clone()); - } - + let source = Some(source.into()); + Shared::get_mut(&mut self.functions) + .as_mut() + .map(|m| m.set_id(source.clone())); + self.source = source; self } /// Clear the source. @@ -910,6 +908,7 @@ impl DerefMut for StmtBlock { impl fmt::Debug for StmtBlock { #[inline(always)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("Block")?; fmt::Debug::fmt(&self.0, f)?; self.1.debug_print(f) } @@ -1226,6 +1225,7 @@ impl Stmt { path: &mut Vec>, on_node: &mut impl FnMut(&[ASTNode]) -> bool, ) -> bool { + // Push the current node onto the path path.push(self.into()); if !on_node(path) { @@ -1341,7 +1341,8 @@ impl Stmt { _ => (), } - path.pop().unwrap(); + path.pop() + .expect("never fails because `path` always contains the current node"); true } @@ -1399,13 +1400,13 @@ impl OpAssignment { pub fn new(op: Token) -> Self { let op_raw = op .map_op_assignment() - .expect("token must be an op-assignment operator") + .expect("never fails because token must be an op-assignment operator") .keyword_syntax(); let op_assignment = op.keyword_syntax(); Self { - hash_op_assign: calc_fn_hash(empty(), op_assignment, 2), - hash_op: calc_fn_hash(empty(), op_raw, 2), + hash_op_assign: calc_fn_hash(op_assignment, 2), + hash_op: calc_fn_hash(op_raw, 2), op: op_assignment, } } @@ -1493,13 +1494,9 @@ impl FnCallHashes { self.script.is_none() } /// Get the script function hash from this [`FnCallHashes`]. - /// - /// # Panics - /// - /// Panics if the [`FnCallHashes`] is native Rust only. #[inline(always)] - pub fn script_hash(&self) -> u64 { - self.script.unwrap() + pub fn script_hash(&self) -> Option { + self.script } /// Get the naive Rust function hash from this [`FnCallHashes`]. #[inline(always)] @@ -1715,7 +1712,13 @@ pub enum Expr { )>, ), /// Property access - ((getter, hash), (setter, hash), prop) - Property(Box<((Identifier, u64), (Identifier, u64), Ident)>), + Property( + Box<( + (Identifier, u64), + (Identifier, u64), + (ImmutableString, Position), + )>, + ), /// { [statement][Stmt] ... } Stmt(Box), /// func `(` expr `,` ... `)` @@ -1780,16 +1783,14 @@ impl fmt::Debug for Expr { } f.write_str(")") } - Self::Property(x) => write!(f, "Property({})", x.2.name), + Self::Property(x) => write!(f, "Property({})", (x.2).0), Self::Stmt(x) => { - f.write_str("Stmt")?; + f.write_str("ExprStmtBlock")?; f.debug_list().entries(x.0.iter()).finish() } Self::FnCall(x, _) => { let mut ff = f.debug_struct("FnCall"); - if let Some(ref ns) = x.namespace { - ff.field("namespace", ns); - } + x.namespace.as_ref().map(|ns| ff.field("namespace", ns)); ff.field("name", &x.name) .field("hash", &x.hashes) .field("args", &x.args); @@ -1843,7 +1844,10 @@ impl Expr { #[cfg(not(feature = "no_index"))] Self::Array(x, _) if self.is_constant() => { let mut arr = Array::with_capacity(x.len()); - arr.extend(x.iter().map(|v| v.get_constant_value().unwrap())); + arr.extend(x.iter().map(|v| { + v.get_constant_value() + .expect("never fails because a constant array always has a constant value") + })); Dynamic::from_array(arr) } @@ -1851,7 +1855,10 @@ impl Expr { Self::Map(x, _) if self.is_constant() => { let mut map = x.1.clone(); x.0.iter().for_each(|(k, v)| { - *map.get_mut(k.name.as_str()).unwrap() = v.get_constant_value().unwrap() + *map.get_mut(k.name.as_str()) + .expect("never fails because the template should contain all the keys") = v + .get_constant_value() + .expect("never fails because a constant map always has a constant value") }); Dynamic::from_map(map) } @@ -1894,9 +1901,14 @@ impl Expr { | Self::FnCall(_, pos) | Self::Custom(_, pos) => *pos, - Self::InterpolatedString(x) => x.first().unwrap().position(), + Self::InterpolatedString(x) => x + .first() + .expect( + "never fails because an interpolated string always contains at least one item", + ) + .position(), - Self::Property(x) => (x.2).pos, + Self::Property(x) => (x.2).1, Self::Stmt(x) => x.1, Self::And(x, _) | Self::Or(x, _) | Self::Dot(x, _) | Self::Index(x, _) => { @@ -1928,10 +1940,12 @@ impl Expr { | Self::Custom(_, pos) => *pos = new_pos, Self::InterpolatedString(x) => { - x.first_mut().unwrap().set_position(new_pos); + x.first_mut() + .expect("never fails because an interpolated string always contains at least one item") + .set_position(new_pos); } - Self::Property(x) => (x.2).pos = new_pos, + Self::Property(x) => (x.2).1 = new_pos, Self::Stmt(x) => x.1 = new_pos, } @@ -2045,6 +2059,7 @@ impl Expr { path: &mut Vec>, on_node: &mut impl FnMut(&[ASTNode]) -> bool, ) -> bool { + // Push the current node onto the path path.push(self.into()); if !on_node(path) { @@ -2098,7 +2113,8 @@ impl Expr { _ => (), } - path.pop().unwrap(); + path.pop() + .expect("never fails because `path` always contains the current node"); true } @@ -2128,7 +2144,7 @@ mod tests { 96 } ); - assert_eq!(size_of::(), 288); + assert_eq!(size_of::(), 160); assert_eq!(size_of::(), 56); assert_eq!( size_of::(), diff --git a/src/dynamic.rs b/src/dynamic.rs index d90b532d..005252f5 100644 --- a/src/dynamic.rs +++ b/src/dynamic.rs @@ -141,6 +141,11 @@ pub enum AccessMode { } /// Arbitrary data attached to a [`Dynamic`] value. +#[cfg(target_pointer_width = "64")] +pub type Tag = i32; + +/// Arbitrary data attached to a [`Dynamic`] value. +#[cfg(target_pointer_width = "32")] pub type Tag = i16; /// Default tag value for [`Dynamic`]. @@ -235,9 +240,10 @@ impl<'d, T: Any + Clone> Deref for DynamicReadLock<'d, T> { fn deref(&self) -> &Self::Target { match &self.0 { DynamicReadLockInner::Reference(reference) => *reference, - // Unwrapping is safe because all checking is already done in its constructor #[cfg(not(feature = "no_closure"))] - DynamicReadLockInner::Guard(guard) => guard.downcast_ref().unwrap(), + DynamicReadLockInner::Guard(guard) => guard.downcast_ref().expect( + "never fails because the read guard was created after checking the data type", + ), } } } @@ -277,9 +283,10 @@ impl<'d, T: Any + Clone> Deref for DynamicWriteLock<'d, T> { fn deref(&self) -> &Self::Target { match &self.0 { DynamicWriteLockInner::Reference(reference) => *reference, - // Unwrapping is safe because all checking is already done in its constructor #[cfg(not(feature = "no_closure"))] - DynamicWriteLockInner::Guard(guard) => guard.downcast_ref().unwrap(), + DynamicWriteLockInner::Guard(guard) => guard.downcast_ref().expect( + "never fails because the write guard was created after checking the data type", + ), } } } @@ -289,16 +296,17 @@ impl<'d, T: Any + Clone> DerefMut for DynamicWriteLock<'d, T> { fn deref_mut(&mut self) -> &mut Self::Target { match &mut self.0 { DynamicWriteLockInner::Reference(reference) => *reference, - // Unwrapping is safe because all checking is already done in its constructor #[cfg(not(feature = "no_closure"))] - DynamicWriteLockInner::Guard(guard) => guard.downcast_mut().unwrap(), + DynamicWriteLockInner::Guard(guard) => guard.downcast_mut().expect( + "never fails because the write guard was created after checking the data type", + ), } } } impl Dynamic { /// Get the arbitrary data attached to this [`Dynamic`]. - pub fn tag(&self) -> Tag { + pub const fn tag(&self) -> Tag { match self.0 { Union::Unit(_, tag, _) | Union::Bool(_, tag, _) @@ -350,7 +358,7 @@ impl Dynamic { /// Does this [`Dynamic`] hold a variant data type /// instead of one of the supported system primitive types? #[inline(always)] - pub fn is_variant(&self) -> bool { + pub const fn is_variant(&self) -> bool { match self.0 { Union::Variant(_, _, _) => true, _ => false, @@ -361,7 +369,7 @@ impl Dynamic { /// Not available under `no_closure`. #[cfg(not(feature = "no_closure"))] #[inline(always)] - pub fn is_shared(&self) -> bool { + pub const fn is_shared(&self) -> bool { #[cfg(not(feature = "no_closure"))] match self.0 { Union::Shared(_, _, _) => return true, @@ -409,7 +417,7 @@ impl Dynamic { #[cfg(not(feature = "no_std"))] Union::TimeStamp(_, _, _) => TypeId::of::(), - Union::Variant(value, _, _) => (***value).type_id(), + Union::Variant(value, _, _) => value.as_ref().as_ref().type_id(), #[cfg(not(feature = "no_closure"))] Union::Shared(cell, _, _) => { @@ -447,7 +455,7 @@ impl Dynamic { #[cfg(not(feature = "no_std"))] Union::TimeStamp(_, _, _) => "timestamp", - Union::Variant(value, _, _) => (***value).type_name(), + Union::Variant(value, _, _) => value.as_ref().as_ref().type_name(), #[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "sync"))] @@ -473,17 +481,12 @@ impl Hash for Dynamic { #[cfg(not(feature = "no_float"))] Union::Float(f, _, _) => f.hash(state), #[cfg(not(feature = "no_index"))] - Union::Array(a, _, _) => (**a).hash(state), + Union::Array(a, _, _) => a.as_ref().hash(state), #[cfg(not(feature = "no_object"))] - Union::Map(m, _, _) => { - let mut buf: crate::StaticVec<_> = m.iter().collect(); - buf.sort_by(|(a, _), (b, _)| a.cmp(b)); - - buf.into_iter().for_each(|(key, value)| { - key.hash(state); - value.hash(state); - }) - } + Union::Map(m, _, _) => m.iter().for_each(|(key, value)| { + key.hash(state); + value.hash(state); + }), Union::FnPtr(f, _, _) if f.is_curried() => { unimplemented!( "{} with curried arguments cannot be hashed", @@ -566,43 +569,47 @@ impl fmt::Display for Dynamic { Union::TimeStamp(_, _, _) => f.write_str(""), Union::Variant(value, _, _) => { - let _type_id = (***value).type_id(); + let value = value.as_ref().as_ref(); + let _type_id = value.type_id(); + let _value_any = value.as_any(); + + const CHECKED: &str = "never fails because the type was checked"; #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] if _type_id == TypeId::of::() { - return write!(f, "{}", (**value).as_any().downcast_ref::().unwrap()); + return fmt::Display::fmt(_value_any.downcast_ref::().expect(CHECKED), f); } else if _type_id == TypeId::of::() { - return write!(f, "{}", (**value).as_any().downcast_ref::().unwrap()); + return fmt::Display::fmt(_value_any.downcast_ref::().expect(CHECKED), f); } else if _type_id == TypeId::of::() { - return write!(f, "{}", (**value).as_any().downcast_ref::().unwrap()); + return fmt::Display::fmt(_value_any.downcast_ref::().expect(CHECKED), f); } else if _type_id == TypeId::of::() { - return write!(f, "{}", (**value).as_any().downcast_ref::().unwrap()); + return fmt::Display::fmt(_value_any.downcast_ref::().expect(CHECKED), f); } else if _type_id == TypeId::of::() { - return write!(f, "{}", (**value).as_any().downcast_ref::().unwrap()); + return fmt::Display::fmt(_value_any.downcast_ref::().expect(CHECKED), f); } else if _type_id == TypeId::of::() { - return write!(f, "{}", (**value).as_any().downcast_ref::().unwrap()); + return fmt::Display::fmt(_value_any.downcast_ref::().expect(CHECKED), f); } else if _type_id == TypeId::of::() { - return write!(f, "{}", (**value).as_any().downcast_ref::().unwrap()); + return fmt::Display::fmt(_value_any.downcast_ref::().expect(CHECKED), f); } else if _type_id == TypeId::of::() { - return write!(f, "{}", (**value).as_any().downcast_ref::().unwrap()); + return fmt::Display::fmt(_value_any.downcast_ref::().expect(CHECKED), f); } #[cfg(not(feature = "no_float"))] if _type_id == TypeId::of::() { - return write!(f, "{}", (**value).as_any().downcast_ref::().unwrap()); + return fmt::Display::fmt(_value_any.downcast_ref::().expect(CHECKED), f); } else if _type_id == TypeId::of::() { - return write!(f, "{}", (**value).as_any().downcast_ref::().unwrap()); + return fmt::Display::fmt(_value_any.downcast_ref::().expect(CHECKED), f); } #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] if _type_id == TypeId::of::() { - return write!(f, "{}", (**value).as_any().downcast_ref::().unwrap()); + return fmt::Display::fmt(_value_any.downcast_ref::().expect(CHECKED), f); } else if _type_id == TypeId::of::() { - return write!(f, "{}", (**value).as_any().downcast_ref::().unwrap()); + return fmt::Display::fmt(_value_any.downcast_ref::().expect(CHECKED), f); } - f.write_str((***value).type_name()) + f.write_str(value.type_name()) } #[cfg(not(feature = "no_closure"))] @@ -645,51 +652,47 @@ impl fmt::Debug for Dynamic { Union::TimeStamp(_, _, _) => write!(f, ""), Union::Variant(value, _, _) => { - let _type_id = (***value).type_id(); + let value = value.as_ref().as_ref(); + let _type_id = value.type_id(); + let _value_any = value.as_any(); + + const CHECKED: &str = "never fails because the type was checked"; #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] if _type_id == TypeId::of::() { - return write!(f, "{:?}", (**value).as_any().downcast_ref::().unwrap()); + return fmt::Debug::fmt(_value_any.downcast_ref::().expect(CHECKED), f); } else if _type_id == TypeId::of::() { - return write!(f, "{:?}", (**value).as_any().downcast_ref::().unwrap()); + return fmt::Debug::fmt(_value_any.downcast_ref::().expect(CHECKED), f); } else if _type_id == TypeId::of::() { - return write!(f, "{:?}", (**value).as_any().downcast_ref::().unwrap()); + return fmt::Debug::fmt(_value_any.downcast_ref::().expect(CHECKED), f); } else if _type_id == TypeId::of::() { - return write!(f, "{:?}", (**value).as_any().downcast_ref::().unwrap()); + return fmt::Debug::fmt(_value_any.downcast_ref::().expect(CHECKED), f); } else if _type_id == TypeId::of::() { - return write!(f, "{:?}", (**value).as_any().downcast_ref::().unwrap()); + return fmt::Debug::fmt(_value_any.downcast_ref::().expect(CHECKED), f); } else if _type_id == TypeId::of::() { - return write!(f, "{:?}", (**value).as_any().downcast_ref::().unwrap()); + return fmt::Debug::fmt(_value_any.downcast_ref::().expect(CHECKED), f); } else if _type_id == TypeId::of::() { - return write!(f, "{:?}", (**value).as_any().downcast_ref::().unwrap()); + return fmt::Debug::fmt(_value_any.downcast_ref::().expect(CHECKED), f); } else if _type_id == TypeId::of::() { - return write!(f, "{:?}", (**value).as_any().downcast_ref::().unwrap()); + return fmt::Debug::fmt(_value_any.downcast_ref::().expect(CHECKED), f); } #[cfg(not(feature = "no_float"))] if _type_id == TypeId::of::() { - return write!(f, "{}", (**value).as_any().downcast_ref::().unwrap()); + return fmt::Debug::fmt(_value_any.downcast_ref::().expect(CHECKED), f); } else if _type_id == TypeId::of::() { - return write!(f, "{}", (**value).as_any().downcast_ref::().unwrap()); + return fmt::Debug::fmt(_value_any.downcast_ref::().expect(CHECKED), f); } #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] if _type_id == TypeId::of::() { - return write!( - f, - "{:?}", - (**value).as_any().downcast_ref::().unwrap() - ); + return fmt::Debug::fmt(_value_any.downcast_ref::().expect(CHECKED), f); } else if _type_id == TypeId::of::() { - return write!( - f, - "{:?}", - (**value).as_any().downcast_ref::().unwrap() - ); + return fmt::Debug::fmt(_value_any.downcast_ref::().expect(CHECKED), f); } - write!(f, "{}", (*value).type_name()) + write!(f, "{}", value.type_name()) } #[cfg(not(feature = "no_closure"))] @@ -746,7 +749,7 @@ impl Clone for Dynamic { } Union::Variant(ref value, tag, _) => { - let mut x = (***value).clone_into_dynamic(); + let mut x = value.as_ref().as_ref().clone_into_dynamic(); x.set_tag(tag); x } @@ -830,7 +833,7 @@ impl Dynamic { )); /// Get the [`AccessMode`] for this [`Dynamic`]. - pub(crate) fn access_mode(&self) -> AccessMode { + pub(crate) const fn access_mode(&self) -> AccessMode { match self.0 { Union::Unit(_, _, access) | Union::Bool(_, _, access) @@ -980,53 +983,40 @@ impl Dynamic { pub fn from(mut value: T) -> Self { // Coded this way in order to maximally leverage potentials for dead-code removal. + const CHECKED: &str = "never fails because the type was checked"; + if TypeId::of::() == TypeId::of::() { - return unsafe_try_cast::<_, Dynamic>(value).ok().unwrap(); + return unsafe_try_cast::<_, Dynamic>(value).ok().expect(CHECKED); } + let val = value.as_any(); + if TypeId::of::() == TypeId::of::() { - return ::downcast_ref::(&value) - .unwrap() - .clone() - .into(); + return val.downcast_ref::().expect(CHECKED).clone().into(); } #[cfg(not(feature = "no_float"))] if TypeId::of::() == TypeId::of::() { - return ::downcast_ref::(&value) - .unwrap() - .clone() - .into(); + return val.downcast_ref::().expect(CHECKED).clone().into(); } #[cfg(feature = "decimal")] if TypeId::of::() == TypeId::of::() { - return ::downcast_ref::(&value) - .unwrap() - .clone() - .into(); + return val.downcast_ref::().expect(CHECKED).clone().into(); } if TypeId::of::() == TypeId::of::() { - return ::downcast_ref::(&value) - .unwrap() - .clone() - .into(); + return val.downcast_ref::().expect(CHECKED).clone().into(); } if TypeId::of::() == TypeId::of::() { - return ::downcast_ref::(&value) - .unwrap() - .clone() - .into(); + return val.downcast_ref::().expect(CHECKED).clone().into(); } if TypeId::of::() == TypeId::of::() { - return ::downcast_ref::(&value) - .unwrap() + return val + .downcast_ref::() + .expect(CHECKED) .clone() .into(); } if TypeId::of::() == TypeId::of::<&str>() { - return ::downcast_ref::<&str>(&value) - .unwrap() - .deref() - .into(); + return val.downcast_ref::<&str>().expect(CHECKED).deref().into(); } if TypeId::of::() == TypeId::of::<()>() { return ().into(); @@ -1450,9 +1440,9 @@ impl Dynamic { #[cfg(feature = "sync")] let value = cell.read().unwrap(); - let type_id = (*value).type_id(); - - if type_id != TypeId::of::() && TypeId::of::() != TypeId::of::() { + if (*value).type_id() != TypeId::of::() + && TypeId::of::() != TypeId::of::() + { return None; } else { return Some(DynamicReadLock(DynamicReadLockInner::Guard(value))); @@ -1483,9 +1473,9 @@ impl Dynamic { #[cfg(feature = "sync")] let value = cell.write().unwrap(); - let type_id = (*value).type_id(); - - if type_id != TypeId::of::() && TypeId::of::() != TypeId::of::() { + if (*value).type_id() != TypeId::of::() + && TypeId::of::() != TypeId::of::() + { return None; } else { return Some(DynamicWriteLock(DynamicWriteLockInner::Guard(value))); @@ -1507,77 +1497,77 @@ impl Dynamic { if TypeId::of::() == TypeId::of::() { return match &self.0 { - Union::Int(value, _, _) => ::downcast_ref::(value), + Union::Int(value, _, _) => value.as_any().downcast_ref::(), _ => None, }; } #[cfg(not(feature = "no_float"))] if TypeId::of::() == TypeId::of::() { return match &self.0 { - Union::Float(value, _, _) => ::downcast_ref::(value.as_ref()), + Union::Float(value, _, _) => value.as_ref().as_any().downcast_ref::(), _ => None, }; } #[cfg(feature = "decimal")] if TypeId::of::() == TypeId::of::() { return match &self.0 { - Union::Decimal(value, _, _) => ::downcast_ref::(value.as_ref()), + Union::Decimal(value, _, _) => value.as_ref().as_any().downcast_ref::(), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match &self.0 { - Union::Bool(value, _, _) => ::downcast_ref::(value), + Union::Bool(value, _, _) => value.as_any().downcast_ref::(), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match &self.0 { - Union::Str(value, _, _) => ::downcast_ref::(value), + Union::Str(value, _, _) => value.as_any().downcast_ref::(), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match &self.0 { - Union::Char(value, _, _) => ::downcast_ref::(value), + Union::Char(value, _, _) => value.as_any().downcast_ref::(), _ => None, }; } #[cfg(not(feature = "no_index"))] if TypeId::of::() == TypeId::of::() { return match &self.0 { - Union::Array(value, _, _) => ::downcast_ref::(value.as_ref()), + Union::Array(value, _, _) => value.as_ref().as_any().downcast_ref::(), _ => None, }; } #[cfg(not(feature = "no_object"))] if TypeId::of::() == TypeId::of::() { return match &self.0 { - Union::Map(value, _, _) => ::downcast_ref::(value.as_ref()), + Union::Map(value, _, _) => value.as_ref().as_any().downcast_ref::(), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match &self.0 { - Union::FnPtr(value, _, _) => ::downcast_ref::(value.as_ref()), + Union::FnPtr(value, _, _) => value.as_ref().as_any().downcast_ref::(), _ => None, }; } #[cfg(not(feature = "no_std"))] if TypeId::of::() == TypeId::of::() { return match &self.0 { - Union::TimeStamp(value, _, _) => ::downcast_ref::(value.as_ref()), + Union::TimeStamp(value, _, _) => value.as_ref().as_any().downcast_ref::(), _ => None, }; } if TypeId::of::() == TypeId::of::<()>() { return match &self.0 { - Union::Unit(value, _, _) => ::downcast_ref::(value), + Union::Unit(value, _, _) => value.as_any().downcast_ref::(), _ => None, }; } if TypeId::of::() == TypeId::of::() { - return ::downcast_ref::(self); + return self.as_any().downcast_ref::(); } match &self.0 { @@ -1597,77 +1587,77 @@ impl Dynamic { if TypeId::of::() == TypeId::of::() { return match &mut self.0 { - Union::Int(value, _, _) => ::downcast_mut::(value), + Union::Int(value, _, _) => value.as_mut_any().downcast_mut::(), _ => None, }; } #[cfg(not(feature = "no_float"))] if TypeId::of::() == TypeId::of::() { return match &mut self.0 { - Union::Float(value, _, _) => ::downcast_mut::(value.as_mut()), + Union::Float(value, _, _) => value.as_mut().as_mut_any().downcast_mut::(), _ => None, }; } #[cfg(feature = "decimal")] if TypeId::of::() == TypeId::of::() { return match &mut self.0 { - Union::Decimal(value, _, _) => ::downcast_mut::(value.as_mut()), + Union::Decimal(value, _, _) => value.as_mut().as_mut_any().downcast_mut::(), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match &mut self.0 { - Union::Bool(value, _, _) => ::downcast_mut::(value), + Union::Bool(value, _, _) => value.as_mut_any().downcast_mut::(), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match &mut self.0 { - Union::Str(value, _, _) => ::downcast_mut::(value), + Union::Str(value, _, _) => value.as_mut_any().downcast_mut::(), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match &mut self.0 { - Union::Char(value, _, _) => ::downcast_mut::(value), + Union::Char(value, _, _) => value.as_mut_any().downcast_mut::(), _ => None, }; } #[cfg(not(feature = "no_index"))] if TypeId::of::() == TypeId::of::() { return match &mut self.0 { - Union::Array(value, _, _) => ::downcast_mut::(value.as_mut()), + Union::Array(value, _, _) => value.as_mut().as_mut_any().downcast_mut::(), _ => None, }; } #[cfg(not(feature = "no_object"))] if TypeId::of::() == TypeId::of::() { return match &mut self.0 { - Union::Map(value, _, _) => ::downcast_mut::(value.as_mut()), + Union::Map(value, _, _) => value.as_mut().as_mut_any().downcast_mut::(), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match &mut self.0 { - Union::FnPtr(value, _, _) => ::downcast_mut::(value.as_mut()), + Union::FnPtr(value, _, _) => value.as_mut().as_mut_any().downcast_mut::(), _ => None, }; } #[cfg(not(feature = "no_std"))] if TypeId::of::() == TypeId::of::() { return match &mut self.0 { - Union::TimeStamp(value, _, _) => ::downcast_mut::(value.as_mut()), + Union::TimeStamp(value, _, _) => value.as_mut().as_mut_any().downcast_mut::(), _ => None, }; } if TypeId::of::() == TypeId::of::<()>() { return match &mut self.0 { - Union::Unit(value, _, _) => ::downcast_mut::(value), + Union::Unit(value, _, _) => value.as_mut_any().downcast_mut::(), _ => None, }; } if TypeId::of::() == TypeId::of::() { - return ::downcast_mut::(self); + return self.as_mut_any().downcast_mut::(); } match &mut self.0 { @@ -1720,8 +1710,8 @@ impl Dynamic { #[cfg(feature = "decimal")] #[inline(always)] pub fn as_decimal(&self) -> Result { - match &self.0 { - Union::Decimal(n, _, _) => Ok(**n), + match self.0 { + Union::Decimal(ref n, _, _) => Ok(**n), #[cfg(not(feature = "no_closure"))] Union::Shared(_, _, _) => self.read_lock().map(|v| *v).ok_or_else(|| self.type_name()), _ => Err(self.type_name()), @@ -1757,8 +1747,8 @@ impl Dynamic { /// Panics if the value is shared. #[inline(always)] pub(crate) fn as_str_ref(&self) -> Result<&str, &'static str> { - match &self.0 { - Union::Str(s, _, _) => Ok(s), + match self.0 { + Union::Str(ref s, _, _) => Ok(s), #[cfg(not(feature = "no_closure"))] Union::Shared(_, _, _) => panic!("as_str() cannot be called on shared values"), _ => Err(self.type_name()), diff --git a/src/engine.rs b/src/engine.rs index fe2bcc7a..63f9ed23 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -29,7 +29,7 @@ use std::{ }; #[cfg(not(feature = "no_index"))] -use crate::{calc_fn_hash, Array}; +use crate::Array; #[cfg(not(feature = "no_object"))] use crate::Map; @@ -224,9 +224,9 @@ pub const KEYWORD_GLOBAL: &str = "global"; pub const FN_GET: &str = "get$"; #[cfg(not(feature = "no_object"))] pub const FN_SET: &str = "set$"; -#[cfg(not(feature = "no_index"))] +#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] pub const FN_IDX_GET: &str = "index$get$"; -#[cfg(not(feature = "no_index"))] +#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] pub const FN_IDX_SET: &str = "index$set$"; #[cfg(not(feature = "no_function"))] pub const FN_ANONYMOUS: &str = "anon$"; @@ -429,15 +429,15 @@ impl<'a> Target<'a> { /// This has no effect except for string indexing. #[cfg(not(feature = "no_object"))] #[inline(always)] - pub fn propagate_changed_value(&mut self) { + pub fn propagate_changed_value(&mut self) -> Result<(), Box> { match self { - Self::Ref(_) | Self::Value(_) => (), + Self::Ref(_) | Self::Value(_) => Ok(()), #[cfg(not(feature = "no_closure"))] - Self::LockGuard(_) => (), + Self::LockGuard(_) => Ok(()), #[cfg(not(feature = "no_index"))] Self::StringChar(_, _, ch) => { let char_value = ch.clone(); - self.set_value(char_value, Position::NONE).unwrap(); + self.set_value(char_value, Position::NONE) } } } @@ -455,7 +455,9 @@ impl<'a> Target<'a> { Self::Value(_) => panic!("cannot update a value"), #[cfg(not(feature = "no_index"))] Self::StringChar(s, index, _) => { - let s = &mut *s.write_lock::().unwrap(); + let s = &mut *s + .write_lock::() + .expect("never fails because `StringChar` always holds an `ImmutableString`"); // Replace the character at the specified index position let new_ch = new_val.as_char().map_err(|err| { @@ -488,7 +490,12 @@ impl<'a> From<&'a mut Dynamic> for Target<'a> { if value.is_shared() { // Cloning is cheap for a shared value let container = value.clone(); - return Self::LockGuard((value.write_lock::().unwrap(), container)); + return Self::LockGuard(( + value + .write_lock::() + .expect("never fails when casting to `Dynamic`"), + container, + )); } Self::Ref(value) @@ -598,9 +605,12 @@ impl State { #[inline(always)] pub fn fn_resolution_cache_mut(&mut self) -> &mut FnResolutionCache { if self.fn_resolution_caches.0.is_empty() { + // Push a new function resolution cache if the stack is empty self.fn_resolution_caches.0.push(BTreeMap::new()); } - self.fn_resolution_caches.0.last_mut().unwrap() + self.fn_resolution_caches.0.last_mut().expect( + "never fails because there is at least one function resolution cache by this point", + ) } /// Push an empty function resolution cache onto the stack and make it current. #[allow(dead_code)] @@ -617,7 +627,11 @@ impl State { /// Panics if there are no more function resolution cache in the stack. #[inline(always)] pub fn pop_fn_resolution_cache(&mut self) { - let mut cache = self.fn_resolution_caches.0.pop().unwrap(); + let mut cache = self + .fn_resolution_caches + .0 + .pop() + .expect("there should be at least one function resolution cache"); cache.clear(); self.fn_resolution_caches.1.push(cache); } @@ -899,11 +913,7 @@ impl Engine { progress: None, // optimization level - optimization_level: if cfg!(feature = "no_optimize") { - OptimizationLevel::None - } else { - OptimizationLevel::Simple - }, + optimization_level: Default::default(), #[cfg(not(feature = "unchecked"))] limits: Limits { @@ -956,11 +966,7 @@ impl Engine { #[cfg(not(feature = "unchecked"))] progress: None, - optimization_level: if cfg!(feature = "no_optimize") { - OptimizationLevel::None - } else { - OptimizationLevel::Simple - }, + optimization_level: Default::default(), #[cfg(not(feature = "unchecked"))] limits: Limits { @@ -1004,10 +1010,16 @@ impl Engine { if let Some(index) = index { let offset = mods.len() - index.get(); - Some(mods.get(offset).expect("invalid index in Imports")) + Some( + mods.get(offset) + .expect("never fails because offset should be within range"), + ) } else { mods.find(root) - .map(|n| mods.get(n).expect("invalid index in Imports")) + .map(|n| { + mods.get(n) + .expect("never fails because the index came from `find`") + }) .or_else(|| self.global_sub_modules.get(root).cloned()) } } @@ -1059,6 +1071,10 @@ impl Engine { } /// Search for a variable within the scope + /// + /// # Panics + /// + /// Panics if `expr` is not [`Expr::Variable`]. pub(crate) fn search_scope_only<'s>( &self, scope: &'s mut Scope, @@ -1096,9 +1112,13 @@ impl Engine { this_ptr, level: 0, }; - if let Some(mut result) = - resolve_var(expr.get_variable_name(true).unwrap(), index, &context) - .map_err(|err| err.fill_position(var_pos))? + if let Some(mut result) = resolve_var( + expr.get_variable_name(true) + .expect("`expr` should be `Variable`"), + index, + &context, + ) + .map_err(|err| err.fill_position(var_pos))? { result.set_access_mode(AccessMode::ReadOnly); return Ok((result.into(), var_pos)); @@ -1109,7 +1129,9 @@ impl Engine { scope.len() - index } else { // Find the variable in the scope - let var_name = expr.get_variable_name(true).unwrap(); + let var_name = expr + .get_variable_name(true) + .expect("`expr` should be `Variable`"); scope .get_index(var_name) .ok_or_else(|| EvalAltResult::ErrorVariableNotFound(var_name.to_string(), var_pos))? @@ -1138,18 +1160,22 @@ impl Engine { level: usize, new_val: Option<((Dynamic, Position), (Option, Position))>, ) -> Result<(Dynamic, bool), Box> { + fn match_chain_type(expr: &Expr) -> ChainType { + match expr { + #[cfg(not(feature = "no_index"))] + Expr::Index(_, _) => ChainType::Index, + #[cfg(not(feature = "no_object"))] + Expr::Dot(_, _) => ChainType::Dot, + _ => unreachable!("`expr` should only be `Index` or `Dot`, but got {:?}", expr), + } + } + let is_ref = target.is_ref(); - let rhs_chain = match rhs { - #[cfg(not(feature = "no_index"))] - Expr::Index(_, _) => Some(ChainType::Index), - #[cfg(not(feature = "no_object"))] - Expr::Dot(_, _) => Some(ChainType::Dot), - _ => None, - }; - // Pop the last index value - let idx_val = idx_values.pop().unwrap(); + let idx_val = idx_values + .pop() + .expect("never fails because an index chain is never empty"); match chain_type { #[cfg(not(feature = "no_index"))] @@ -1162,9 +1188,9 @@ impl Engine { let idx_pos = x.lhs.position(); let idx_val = idx_val.as_index_value(); let obj_ptr = &mut self.get_indexed_mut( - mods, state, lib, target, idx_val, idx_pos, false, is_ref, true, level, + mods, state, lib, target, idx_val, idx_pos, false, true, level, )?; - let rhs_chain = rhs_chain.unwrap(); + let rhs_chain = match_chain_type(rhs); self.eval_dot_index_chain_helper( mods, state, lib, this_ptr, obj_ptr, root, &x.rhs, idx_values, @@ -1174,69 +1200,48 @@ impl Engine { } // xxx[rhs] op= new_val _ if new_val.is_some() => { + let ((mut new_val, new_pos), (op_info, op_pos)) = + new_val.expect("never fails because `new_val` is `Some`"); let idx_val = idx_val.as_index_value(); #[cfg(not(feature = "no_index"))] - let mut idx_val2 = idx_val.clone(); + let mut idx_val_for_setter = idx_val.clone(); - // `call_setter` is introduced to bypass double mutable borrowing of target - let _call_setter = match self.get_indexed_mut( - mods, state, lib, target, idx_val, pos, true, is_ref, false, level, + match self.get_indexed_mut( + mods, state, lib, target, idx_val, pos, true, false, level, ) { // Indexed value is a reference - update directly Ok(obj_ptr) => { - let ((new_val, new_pos), (op_info, op_pos)) = new_val.unwrap(); self.eval_op_assignment( mods, state, lib, op_info, op_pos, obj_ptr, root, new_val, new_pos, )?; - None + return Ok((Dynamic::UNIT, true)); } - Err(err) => match *err { - // No index getter - try to call an index setter - #[cfg(not(feature = "no_index"))] - EvalAltResult::ErrorIndexingType(_, _) => Some(new_val.unwrap()), - // Any other error - return - err => return err.into(), - }, - }; - - #[cfg(not(feature = "no_index"))] - if let Some(mut new_val) = _call_setter { - let val_type_name = target.type_name(); - let ((_, val_pos), _) = new_val; - - let hash_set = FnCallHashes::from_native(calc_fn_hash( - std::iter::empty(), - FN_IDX_SET, - 3, - )); - let args = &mut [target, &mut idx_val2, &mut (new_val.0).0]; - - self.exec_fn_call( - mods, state, lib, FN_IDX_SET, hash_set, args, is_ref, true, - val_pos, None, level, - ) - .map_err(|err| match *err { - EvalAltResult::ErrorFunctionNotFound(fn_sig, _) - if fn_sig.ends_with("]=") => - { - EvalAltResult::ErrorIndexingType( - self.map_type_name(val_type_name).into(), - Position::NONE, - ) - } - err => err, - })?; + // Can't index - try to call an index setter + #[cfg(not(feature = "no_index"))] + Err(err) if matches!(*err, EvalAltResult::ErrorIndexingType(_, _)) => {} + // Any other error + Err(err) => return Err(err), } + // Try to call index setter + let hash_set = + FnCallHashes::from_native(crate::calc_fn_hash(FN_IDX_SET, 3)); + let args = &mut [target, &mut idx_val_for_setter, &mut new_val]; + + self.exec_fn_call( + mods, state, lib, FN_IDX_SET, hash_set, args, is_ref, true, new_pos, + None, level, + )?; + Ok((Dynamic::UNIT, true)) } // xxx[rhs] _ => { let idx_val = idx_val.as_index_value(); self.get_indexed_mut( - mods, state, lib, target, idx_val, pos, false, is_ref, true, level, + mods, state, lib, target, idx_val, pos, false, true, level, ) .map(|v| (v.take_or_clone(), false)) } @@ -1264,12 +1269,13 @@ impl Engine { } // {xxx:map}.id op= ??? Expr::Property(x) if target.is::() && new_val.is_some() => { - let Ident { name, pos, .. } = &x.2; + let (name, pos) = &x.2; let index = name.into(); let val = self.get_indexed_mut( - mods, state, lib, target, index, *pos, true, is_ref, false, level, + mods, state, lib, target, index, *pos, true, false, level, )?; - let ((new_val, new_pos), (op_info, op_pos)) = new_val.unwrap(); + let ((new_val, new_pos), (op_info, op_pos)) = + new_val.expect("never fails because `new_val` is `Some`"); self.eval_op_assignment( mods, state, lib, op_info, op_pos, val, root, new_val, new_pos, )?; @@ -1277,27 +1283,46 @@ impl Engine { } // {xxx:map}.id Expr::Property(x) if target.is::() => { - let Ident { name, pos, .. } = &x.2; + let (name, pos) = &x.2; let index = name.into(); let val = self.get_indexed_mut( - mods, state, lib, target, index, *pos, false, is_ref, false, level, + mods, state, lib, target, index, *pos, false, false, level, )?; Ok((val.take_or_clone(), false)) } // xxx.id op= ??? Expr::Property(x) if new_val.is_some() => { - let ((getter, hash_get), (setter, hash_set), Ident { pos, .. }) = - x.as_ref(); - let ((mut new_val, new_pos), (op_info, op_pos)) = new_val.unwrap(); + let ((getter, hash_get), (setter, hash_set), (name, pos)) = x.as_ref(); + let ((mut new_val, new_pos), (op_info, op_pos)) = + new_val.expect("never fails because `new_val` is `Some`"); if op_info.is_some() { let hash = FnCallHashes::from_native(*hash_get); let mut args = [target.as_mut()]; - let (mut orig_val, _) = self.exec_fn_call( - mods, state, lib, getter, hash, &mut args, is_ref, true, *pos, - None, level, - )?; + let (mut orig_val, _) = self + .exec_fn_call( + mods, state, lib, getter, hash, &mut args, is_ref, true, *pos, + None, level, + ) + .or_else(|err| match *err { + // Try an indexer if property does not exist + EvalAltResult::ErrorDotExpr(_, _) => { + let prop = name.into(); + self.get_indexed_mut( + mods, state, lib, target, prop, *pos, false, true, + level, + ) + .map(|v| (v.take_or_clone(), false)) + .map_err( + |idx_err| match *idx_err { + EvalAltResult::ErrorIndexingType(_, _) => err, + _ => idx_err, + }, + ) + } + _ => Err(err), + })?; let obj_ptr = (&mut orig_val).into(); self.eval_op_assignment( mods, state, lib, op_info, op_pos, obj_ptr, root, new_val, new_pos, @@ -1311,28 +1336,65 @@ impl Engine { mods, state, lib, setter, hash, &mut args, is_ref, true, *pos, None, level, ) - .map(|(v, _)| (v, true)) + .or_else(|err| match *err { + // Try an indexer if property does not exist + EvalAltResult::ErrorDotExpr(_, _) => { + let mut prop = name.into(); + let args = &mut [target, &mut prop, &mut new_val]; + let hash_set = + FnCallHashes::from_native(crate::calc_fn_hash(FN_IDX_SET, 3)); + self.exec_fn_call( + mods, state, lib, FN_IDX_SET, hash_set, args, is_ref, true, + *pos, None, level, + ) + .map_err( + |idx_err| match *idx_err { + EvalAltResult::ErrorIndexingType(_, _) => err, + _ => idx_err, + }, + ) + } + _ => Err(err), + }) } // xxx.id Expr::Property(x) => { - let ((getter, hash_get), _, Ident { pos, .. }) = x.as_ref(); + let ((getter, hash_get), _, (name, pos)) = x.as_ref(); let hash = FnCallHashes::from_native(*hash_get); let mut args = [target.as_mut()]; self.exec_fn_call( mods, state, lib, getter, hash, &mut args, is_ref, true, *pos, None, level, ) - .map(|(v, _)| (v, false)) + .map_or_else( + |err| match *err { + // Try an indexer if property does not exist + EvalAltResult::ErrorDotExpr(_, _) => { + let prop = name.into(); + self.get_indexed_mut( + mods, state, lib, target, prop, *pos, false, true, level, + ) + .map(|v| (v.take_or_clone(), false)) + .map_err(|idx_err| { + match *idx_err { + EvalAltResult::ErrorIndexingType(_, _) => err, + _ => idx_err, + } + }) + } + _ => Err(err), + }, + |(v, _)| Ok((v, false)), + ) } // {xxx:map}.sub_lhs[expr] | {xxx:map}.sub_lhs.expr Expr::Index(x, x_pos) | Expr::Dot(x, x_pos) if target.is::() => { let mut val = match &x.lhs { Expr::Property(p) => { - let Ident { name, pos, .. } = &p.2; + let (name, pos) = &p.2; let index = name.into(); self.get_indexed_mut( - mods, state, lib, target, index, *pos, false, is_ref, true, - level, + mods, state, lib, target, index, *pos, false, true, level, )? } // {xxx:map}.fn_name(arg_expr_list)[expr] | {xxx:map}.fn_name(arg_expr_list).expr @@ -1351,7 +1413,7 @@ impl Engine { // Others - syntax error expr => unreachable!("invalid dot expression: {:?}", expr), }; - let rhs_chain = rhs_chain.unwrap(); + let rhs_chain = match_chain_type(rhs); self.eval_dot_index_chain_helper( mods, state, lib, this_ptr, &mut val, root, &x.rhs, idx_values, @@ -1364,17 +1426,36 @@ impl Engine { match &x.lhs { // xxx.prop[expr] | xxx.prop.expr Expr::Property(p) => { - let ((getter, hash_get), (setter, hash_set), Ident { pos, .. }) = + let ((getter, hash_get), (setter, hash_set), (name, pos)) = p.as_ref(); - let rhs_chain = rhs_chain.unwrap(); + let rhs_chain = match_chain_type(rhs); let hash_get = FnCallHashes::from_native(*hash_get); let hash_set = FnCallHashes::from_native(*hash_set); - let arg_values = &mut [target.as_mut(), &mut Default::default()]; + let mut arg_values = [target.as_mut(), &mut Default::default()]; let args = &mut arg_values[..1]; - let (mut val, updated) = self.exec_fn_call( - mods, state, lib, getter, hash_get, args, is_ref, true, *pos, - None, level, - )?; + let (mut val, updated) = self + .exec_fn_call( + mods, state, lib, getter, hash_get, args, is_ref, true, + *pos, None, level, + ) + .or_else(|err| match *err { + // Try an indexer if property does not exist + EvalAltResult::ErrorDotExpr(_, _) => { + let prop = name.into(); + self.get_indexed_mut( + mods, state, lib, target, prop, *pos, false, true, + level, + ) + .map(|v| (v.take_or_clone(), false)) + .map_err( + |idx_err| match *idx_err { + EvalAltResult::ErrorIndexingType(_, _) => err, + _ => idx_err, + }, + ) + } + _ => Err(err), + })?; let val = &mut val; @@ -1397,17 +1478,33 @@ impl Engine { // Feed the value back via a setter just in case it has been updated if updated || may_be_changed { // Re-use args because the first &mut parameter will not be consumed - arg_values[1] = val; + let mut arg_values = [target.as_mut(), val]; + let args = &mut arg_values; self.exec_fn_call( - mods, state, lib, setter, hash_set, arg_values, is_ref, - true, *pos, None, level, + mods, state, lib, setter, hash_set, args, is_ref, true, + *pos, None, level, ) .or_else( |err| match *err { - // If there is no setter, no need to feed it back because - // the property is read-only + // Try an indexer if property does not exist EvalAltResult::ErrorDotExpr(_, _) => { - Ok((Dynamic::UNIT, false)) + let mut prop = name.into(); + let args = &mut [target.as_mut(), &mut prop, val]; + let hash_set = FnCallHashes::from_native( + crate::calc_fn_hash(FN_IDX_SET, 3), + ); + self.exec_fn_call( + mods, state, lib, FN_IDX_SET, hash_set, args, + is_ref, true, *pos, None, level, + ) + .or_else(|idx_err| match *idx_err { + EvalAltResult::ErrorIndexingType(_, _) => { + // If there is no setter, no need to feed it back because + // the property is read-only + Ok((Dynamic::UNIT, false)) + } + _ => Err(idx_err), + }) } _ => Err(err), }, @@ -1419,7 +1516,7 @@ impl Engine { // xxx.fn_name(arg_expr_list)[expr] | xxx.fn_name(arg_expr_list).expr Expr::FnCall(f, pos) if !f.is_qualified() => { let FnCallExpr { name, hashes, .. } = f.as_ref(); - let rhs_chain = rhs_chain.unwrap(); + let rhs_chain = match_chain_type(rhs); let mut args = idx_val.as_fn_call_args(); let (mut val, _) = self.make_method_call( mods, state, lib, name, *hashes, target, &mut args, *pos, level, @@ -1558,7 +1655,7 @@ impl Engine { #[cfg(not(feature = "no_object"))] Expr::Property(x) if _parent_chain_type == ChainType::Dot => { - idx_values.push(ChainArgument::Property(x.2.pos)) + idx_values.push(ChainArgument::Property((x.2).1)) } Expr::Property(_) => unreachable!("unexpected Expr::Property for indexing"), @@ -1569,7 +1666,7 @@ impl Engine { let lhs_val = match lhs { #[cfg(not(feature = "no_object"))] Expr::Property(x) if _parent_chain_type == ChainType::Dot => { - ChainArgument::Property(x.2.pos) + ChainArgument::Property((x.2).1) } Expr::Property(_) => unreachable!("unexpected Expr::Property for indexing"), @@ -1646,16 +1743,15 @@ impl Engine { #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] fn get_indexed_mut<'t>( &self, - _mods: &mut Imports, + mods: &mut Imports, state: &mut State, - _lib: &[&Module], + lib: &[&Module], target: &'t mut Dynamic, - mut _idx: Dynamic, + mut idx: Dynamic, idx_pos: Position, _create: bool, - _is_ref: bool, - _indexers: bool, - _level: usize, + indexers: bool, + level: usize, ) -> Result, Box> { #[cfg(not(feature = "unchecked"))] self.inc_operations(state, Position::NONE)?; @@ -1664,7 +1760,7 @@ impl Engine { #[cfg(not(feature = "no_index"))] Dynamic(Union::Array(arr, _, _)) => { // val_array[idx] - let index = _idx + let index = idx .as_int() .map_err(|err| self.make_type_mismatch_err::(err, idx_pos))?; @@ -1706,8 +1802,8 @@ impl Engine { #[cfg(not(feature = "no_object"))] Dynamic(Union::Map(map, _, _)) => { // val_map[idx] - let index = &*_idx.read_lock::().ok_or_else(|| { - self.make_type_mismatch_err::(_idx.type_name(), idx_pos) + let index = &*idx.read_lock::().ok_or_else(|| { + self.make_type_mismatch_err::(idx.type_name(), idx_pos) })?; if _create && !map.contains_key(index.as_str()) { @@ -1723,7 +1819,7 @@ impl Engine { #[cfg(not(feature = "no_index"))] Dynamic(Union::Str(s, _, _)) => { // val_string[idx] - let index = _idx + let index = idx .as_int() .map_err(|err| self.make_type_mismatch_err::(err, idx_pos))?; @@ -1754,27 +1850,14 @@ impl Engine { Ok(Target::StringChar(target, offset, ch.into())) } - #[cfg(not(feature = "no_index"))] - _ if _indexers => { - let type_name = target.type_name(); - let args = &mut [target, &mut _idx]; - let hash_get = - FnCallHashes::from_native(calc_fn_hash(std::iter::empty(), FN_IDX_GET, 2)); + _ if indexers => { + let args = &mut [target, &mut idx]; + let hash_get = FnCallHashes::from_native(crate::calc_fn_hash(FN_IDX_GET, 2)); self.exec_fn_call( - _mods, state, _lib, FN_IDX_GET, hash_get, args, _is_ref, true, idx_pos, None, - _level, + mods, state, lib, FN_IDX_GET, hash_get, args, true, true, idx_pos, None, level, ) .map(|(v, _)| v.into()) - .map_err(|err| match *err { - EvalAltResult::ErrorFunctionNotFound(fn_sig, _) if fn_sig.ends_with(']') => { - Box::new(EvalAltResult::ErrorIndexingType( - type_name.into(), - Position::NONE, - )) - } - _ => err, - }) } _ => EvalAltResult::ErrorIndexingType( @@ -1878,7 +1961,10 @@ impl Engine { Expr::Map(x, _) => { let mut map = x.1.clone(); for (Ident { name: key, .. }, expr) in &x.0 { - *map.get_mut(key.as_str()).unwrap() = self + let value_ref = map + .get_mut(key.as_str()) + .expect("never fails because the template should contain all the keys"); + *value_ref = self .eval_expr(scope, mods, state, lib, this_ptr, expr, level)? .flatten(); } @@ -1895,7 +1981,9 @@ impl Engine { literal_args: c_args, .. } = x.as_ref(); - let namespace = namespace.as_ref(); + let namespace = namespace + .as_ref() + .expect("never fails because function call is qualified"); let hash = hashes.native_hash(); self.make_qualified_function_call( scope, mods, state, lib, this_ptr, namespace, name, args, c_args, hash, *pos, @@ -1954,10 +2042,12 @@ impl Engine { .iter() .map(Into::into) .collect::>(); + let key_token = custom.tokens.first().expect( + "never fails because a custom syntax stream must contain at least one token", + ); let custom_def = self - .custom_syntax - .get(custom.tokens.first().unwrap()) - .unwrap(); + .custom_syntax.get(key_token) + .expect("never fails because the custom syntax leading token should match with definition"); let mut context = EvalContext { engine: self, scope, @@ -2088,7 +2178,9 @@ impl Engine { let target_is_shared = false; if target_is_shared { - lock_guard = target.write_lock::().unwrap(); + lock_guard = target + .write_lock::() + .expect("never fails when casting to `Dynamic`"); lhs_ptr_inner = &mut *lock_guard; } else { lhs_ptr_inner = &mut *target; @@ -2158,12 +2250,13 @@ impl Engine { let (lhs_ptr, pos) = self.search_namespace(scope, mods, state, lib, this_ptr, lhs_expr)?; + let var_name = lhs_expr + .get_variable_name(false) + .expect("never fails because `lhs_ptr` is a `Variable`s"); + if !lhs_ptr.is_ref() { - return EvalAltResult::ErrorAssignmentToConstant( - lhs_expr.get_variable_name(false).unwrap().to_string(), - pos, - ) - .into(); + return EvalAltResult::ErrorAssignmentToConstant(var_name.to_string(), pos) + .into(); } #[cfg(not(feature = "unchecked"))] @@ -2176,7 +2269,7 @@ impl Engine { op_info.clone(), *op_pos, lhs_ptr, - (lhs_expr.get_variable_name(false).unwrap(), pos), + (var_name, pos), rhs_val, rhs_expr.position(), )?; @@ -2401,7 +2494,10 @@ impl Engine { let loop_var_is_shared = false; if loop_var_is_shared { - *loop_var.write_lock().unwrap() = value; + let mut value_ref = loop_var + .write_lock() + .expect("never fails when casting to `Dynamic`"); + *value_ref = value; } else { *loop_var = value; } @@ -2451,7 +2547,9 @@ impl Engine { literal_args: c_args, .. } = x.as_ref(); - let namespace = namespace.as_ref(); + let namespace = namespace + .as_ref() + .expect("never fails because function call is qualified"); let hash = hashes.native_hash(); self.make_qualified_function_call( scope, mods, state, lib, this_ptr, namespace, name, args, c_args, hash, *pos, @@ -2505,26 +2603,22 @@ impl Engine { err_map.insert("message".into(), err.to_string().into()); - if let Some(ref source) = state.source { - err_map.insert("source".into(), source.into()); - } + state + .source + .as_ref() + .map(|source| err_map.insert("source".into(), source.into())); if err_pos.is_none() { // No position info } else { - err_map.insert( - "line".into(), - (err_pos.line().unwrap() as INT).into(), - ); - err_map.insert( - "position".into(), - if err_pos.is_beginning_of_line() { - 0 - } else { - err_pos.position().unwrap() as INT - } - .into(), - ); + let line = err_pos.line().expect("never fails because a non-NONE `Position` always has a line number") as INT; + let position = if err_pos.is_beginning_of_line() { + 0 + } else { + err_pos.position().expect("never fails because a non-NONE `Position` always has a character position") + } as INT; + err_map.insert("line".into(), line.into()); + err_map.insert("position".into(), position.into()); } err.dump_fields(&mut err_map); @@ -2535,9 +2629,9 @@ impl Engine { let orig_scope_len = scope.len(); state.scope_level += 1; - if let Some(Ident { name, .. }) = err_var { - scope.push(unsafe_cast_var_name_to_lifetime(&name), err_value); - } + err_var.as_ref().map(|Ident { name, .. }| { + scope.push(unsafe_cast_var_name_to_lifetime(name), err_value) + }); let result = self.eval_stmt_block( scope, mods, state, lib, this_ptr, catch_stmt, true, level, @@ -2604,7 +2698,10 @@ impl Engine { #[cfg(not(feature = "no_function"))] if entry_type == AccessMode::ReadOnly && lib.iter().any(|&m| !m.is_empty()) { let global = if let Some(index) = mods.find(KEYWORD_GLOBAL) { - match mods.get_mut(index).unwrap() { + match mods + .get_mut(index) + .expect("never fails because the index came from `find`") + { m if m.internal => Some(m), _ => None, } @@ -2613,12 +2710,16 @@ impl Engine { let mut global = Module::new(); global.internal = true; mods.push(KEYWORD_GLOBAL, global); - Some(mods.get_mut(mods.len() - 1).unwrap()) + Some( + mods.get_mut(mods.len() - 1) + .expect("never fails because the global module was just added"), + ) }; if let Some(global) = global { - let global = Shared::get_mut(global).unwrap(); - global.set_var(name.clone(), value.clone()); + Shared::get_mut(global) + .expect("never fails because the global module is never shared") + .set_var(name.clone(), value.clone()); } } @@ -2635,9 +2736,8 @@ impl Engine { scope.push_dynamic_value(var_name, entry_type, value); #[cfg(not(feature = "no_module"))] - if let Some(alias) = _alias { - scope.add_entry_alias(scope.len() - 1, alias); - } + _alias.map(|alias| scope.add_entry_alias(scope.len() - 1, alias)); + Ok(Dynamic::UNIT) } @@ -2673,7 +2773,7 @@ impl Engine { self.module_resolver.resolve(self, source, &path, expr_pos) })?; - if let Some(name) = export.as_ref().map(|x| x.name.clone()) { + export.as_ref().map(|x| x.name.clone()).map(|name| { if !module.is_indexed() { // Index the module (making a clone copy if necessary) if it is not indexed let mut module = crate::fn_native::shared_take_or_clone(module); @@ -2682,7 +2782,7 @@ impl Engine { } else { mods.push(name, module); } - } + }); state.modules += 1; @@ -2710,14 +2810,14 @@ impl Engine { // Share statement #[cfg(not(feature = "no_closure"))] Stmt::Share(name) => { - if let Some((index, _)) = scope.get_index(name) { + scope.get_index(name).map(|(index, _)| { let val = scope.get_mut_by_index(index); if !val.is_shared() { // Replace the variable with a shared value. *val = std::mem::take(val).into_shared(); } - } + }); Ok(Dynamic::UNIT) } }; @@ -2732,9 +2832,10 @@ impl Engine { /// Check a result to ensure that the data size is within allowable limit. #[cfg(not(feature = "unchecked"))] fn check_data_size(&self, result: &RhaiResult) -> Result<(), Box> { - if result.is_err() { - return Ok(()); - } + let result = match result { + Err(_) => return Ok(()), + Ok(r) => r, + }; // If no data size limits, just return let mut _has_limit = self.limits.max_string_size.is_some(); @@ -2803,7 +2904,7 @@ impl Engine { } } - let (_arr, _map, s) = calc_size(result.as_ref().unwrap()); + let (_arr, _map, s) = calc_size(result); if s > self .limits @@ -2860,7 +2961,7 @@ impl Engine { } // Report progress - only in steps - if let Some(progress) = &self.progress { + if let Some(ref progress) = self.progress { if let Some(token) = progress(state.operations) { // Terminate script if progress returns a termination token return EvalAltResult::ErrorTerminated(token, pos).into(); diff --git a/src/engine_api.rs b/src/engine_api.rs index 152a5338..6a847647 100644 --- a/src/engine_api.rs +++ b/src/engine_api.rs @@ -477,9 +477,7 @@ impl Engine { set_fn: impl Fn(&mut T, V) -> Result<(), Box> + SendSync + 'static, ) -> &mut Self { use crate::engine::make_setter; - self.register_result_fn(&make_setter(name), move |obj: &mut T, value: V| { - set_fn(obj, value) - }) + self.register_result_fn(&make_setter(name), set_fn) } /// Short-hand for registering both getter and setter functions /// of a registered type with the [`Engine`]. @@ -536,7 +534,7 @@ impl Engine { /// /// The function signature must start with `&mut self` and not `&self`. /// - /// Not available under `no_index`. + /// Not available under both `no_index` and `no_object`. /// /// # Panics /// @@ -572,16 +570,18 @@ impl Engine { /// // Register an indexer. /// .register_indexer_get(TestStruct::get_field); /// + /// # #[cfg(not(feature = "no_index"))] /// assert_eq!(engine.eval::("let a = new_ts(); a[2]")?, 3); /// # Ok(()) /// # } /// ``` - #[cfg(not(feature = "no_index"))] + #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[inline(always)] pub fn register_indexer_get( &mut self, get_fn: impl Fn(&mut T, X) -> V + SendSync + 'static, ) -> &mut Self { + #[cfg(not(feature = "no_index"))] if TypeId::of::() == TypeId::of::() { panic!("Cannot register indexer for arrays."); } @@ -602,7 +602,7 @@ impl Engine { /// /// The function signature must start with `&mut self` and not `&self`. /// - /// Not available under `no_index`. + /// Not available under both `no_index` and `no_object`. /// /// # Panics /// @@ -640,11 +640,12 @@ impl Engine { /// // Register an indexer. /// .register_indexer_get_result(TestStruct::get_field); /// + /// # #[cfg(not(feature = "no_index"))] /// assert_eq!(engine.eval::("let a = new_ts(); a[2]")?, 3); /// # Ok(()) /// # } /// ``` - #[cfg(not(feature = "no_index"))] + #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[inline(always)] pub fn register_indexer_get_result< T: Variant + Clone, @@ -654,6 +655,7 @@ impl Engine { &mut self, get_fn: impl Fn(&mut T, X) -> Result> + SendSync + 'static, ) -> &mut Self { + #[cfg(not(feature = "no_index"))] if TypeId::of::() == TypeId::of::() { panic!("Cannot register indexer for arrays."); } @@ -672,7 +674,7 @@ impl Engine { } /// Register an index setter for a custom type with the [`Engine`]. /// - /// Not available under `no_index`. + /// Not available under both `no_index` and `no_object`. /// /// # Panics /// @@ -707,6 +709,7 @@ impl Engine { /// // Register an indexer. /// .register_indexer_set(TestStruct::set_field); /// + /// # #[cfg(not(feature = "no_index"))] /// assert_eq!( /// engine.eval::("let a = new_ts(); a[2] = 42; a")?.fields[2], /// 42 @@ -714,12 +717,13 @@ impl Engine { /// # Ok(()) /// # } /// ``` - #[cfg(not(feature = "no_index"))] + #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[inline(always)] pub fn register_indexer_set( &mut self, set_fn: impl Fn(&mut T, X, V) + SendSync + 'static, ) -> &mut Self { + #[cfg(not(feature = "no_index"))] if TypeId::of::() == TypeId::of::() { panic!("Cannot register indexer for arrays."); } @@ -738,7 +742,7 @@ impl Engine { } /// Register an index setter for a custom type with the [`Engine`]. /// - /// Not available under `no_index`. + /// Not available under both `no_index` and `no_object`. /// /// # Panics /// @@ -776,6 +780,7 @@ impl Engine { /// // Register an indexer. /// .register_indexer_set_result(TestStruct::set_field); /// + /// # #[cfg(not(feature = "no_index"))] /// assert_eq!( /// engine.eval::("let a = new_ts(); a[2] = 42; a")?.fields[2], /// 42 @@ -783,7 +788,7 @@ impl Engine { /// # Ok(()) /// # } /// ``` - #[cfg(not(feature = "no_index"))] + #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[inline(always)] pub fn register_indexer_set_result< T: Variant + Clone, @@ -793,6 +798,7 @@ impl Engine { &mut self, set_fn: impl Fn(&mut T, X, V) -> Result<(), Box> + SendSync + 'static, ) -> &mut Self { + #[cfg(not(feature = "no_index"))] if TypeId::of::() == TypeId::of::() { panic!("Cannot register indexer for arrays."); } @@ -807,14 +813,11 @@ impl Engine { panic!("Cannot register indexer for strings."); } - self.register_result_fn( - crate::engine::FN_IDX_SET, - move |obj: &mut T, index: X, value: V| set_fn(obj, index, value), - ) + self.register_result_fn(crate::engine::FN_IDX_SET, set_fn) } - /// Short-hand for register both index getter and setter functions for a custom type with the [`Engine`]. + /// Short-hand for registering both index getter and setter functions for a custom type with the [`Engine`]. /// - /// Not available under `no_index`. + /// Not available under both `no_index` and `no_object`. /// /// # Panics /// @@ -852,11 +855,12 @@ impl Engine { /// // Register an indexer. /// .register_indexer_get_set(TestStruct::get_field, TestStruct::set_field); /// + /// # #[cfg(not(feature = "no_index"))] /// assert_eq!(engine.eval::("let a = new_ts(); a[2] = 42; a[2]")?, 42); /// # Ok(()) /// # } /// ``` - #[cfg(not(feature = "no_index"))] + #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[inline(always)] pub fn register_indexer_get_set( &mut self, @@ -947,8 +951,14 @@ impl Engine { } } else { let mut iter = name.as_ref().splitn(2, separator.as_ref()); - let sub_module = iter.next().unwrap().trim(); - let remainder = iter.next().unwrap().trim(); + let sub_module = iter + .next() + .expect("never fails because the name contains a separator") + .trim(); + let remainder = iter + .next() + .expect("never fails because the name contains a separator") + .trim(); if !root.contains_key(sub_module) { let mut m: Module = Default::default(); @@ -956,7 +966,9 @@ impl Engine { m.build_index(); root.insert(sub_module.into(), m.into()); } else { - let m = root.remove(sub_module).unwrap(); + let m = root + .remove(sub_module) + .expect("never fails because the root contains the sub-module"); let mut m = crate::fn_native::shared_take_or_clone(m); register_static_module_raw(m.sub_modules_mut(), remainder, module); m.build_index(); @@ -1074,7 +1086,10 @@ impl Engine { resolver: &StaticModuleResolver, imports: &mut BTreeSet, ) { - ast.walk(&mut |path| match path.last().unwrap() { + ast.walk(&mut |path| match path + .last() + .expect("never fails because `path` always contains the current node") + { // Collect all `import` statements with a string constant path ASTNode::Stmt(Stmt::Import(Expr::StringConstant(s, _), _, _)) if !resolver.contains_path(s) && !imports.contains(s.as_str()) => diff --git a/src/fn_builtin.rs b/src/fn_builtin.rs index b95b0fcf..4c86c32b 100644 --- a/src/fn_builtin.rs +++ b/src/fn_builtin.rs @@ -13,6 +13,8 @@ use crate::FLOAT; #[cfg(feature = "decimal")] use rust_decimal::Decimal; +const BUILTIN: &str = "never fails because this is built-in code and the type is already checked"; + /// Is the type a numeric type? #[inline(always)] fn is_numeric(type_id: TypeId) -> bool { @@ -75,22 +77,22 @@ pub fn get_builtin_binary_op_fn( macro_rules! impl_op { ($xx:ident $op:tt $yy:ident) => { return Some(|_, args| { - let x = &*args[0].read_lock::<$xx>().unwrap(); - let y = &*args[1].read_lock::<$yy>().unwrap(); + let x = &*args[0].read_lock::<$xx>().expect(BUILTIN); + let y = &*args[1].read_lock::<$yy>().expect(BUILTIN); Ok((x $op y).into()) }) }; ($xx:ident . $func:ident ( $yy:ty )) => { return Some(|_, args| { - let x = &*args[0].read_lock::<$xx>().unwrap(); - let y = &*args[1].read_lock::<$yy>().unwrap(); + let x = &*args[0].read_lock::<$xx>().expect(BUILTIN); + let y = &*args[1].read_lock::<$yy>().expect(BUILTIN); Ok(x.$func(y).into()) }) }; ($xx:ident . $func:ident ( $yy:ident . $yyy:ident () )) => { return Some(|_, args| { - let x = &*args[0].read_lock::<$xx>().unwrap(); - let y = &*args[1].read_lock::<$yy>().unwrap(); + let x = &*args[0].read_lock::<$xx>().expect(BUILTIN); + let y = &*args[1].read_lock::<$yy>().expect(BUILTIN); Ok(x.$func(y.$yyy()).into()) }) }; @@ -102,43 +104,43 @@ pub fn get_builtin_binary_op_fn( }; ($base:ty => $xx:ident $op:tt $yy:ident) => { return Some(|_, args| { - let x = args[0].$xx().unwrap() as $base; - let y = args[1].$yy().unwrap() as $base; + let x = args[0].$xx().expect(BUILTIN) as $base; + let y = args[1].$yy().expect(BUILTIN) as $base; Ok((x $op y).into()) }) }; ($base:ty => $xx:ident . $func:ident ( $yy:ident as $yyy:ty)) => { return Some(|_, args| { - let x = args[0].$xx().unwrap() as $base; - let y = args[1].$yy().unwrap() as $base; + let x = args[0].$xx().expect(BUILTIN) as $base; + let y = args[1].$yy().expect(BUILTIN) as $base; Ok(x.$func(y as $yyy).into()) }) }; ($base:ty => $func:ident ( $xx:ident, $yy:ident )) => { return Some(|_, args| { - let x = args[0].$xx().unwrap() as $base; - let y = args[1].$yy().unwrap() as $base; + let x = args[0].$xx().expect(BUILTIN) as $base; + let y = args[1].$yy().expect(BUILTIN) as $base; $func(x, y).map(Into::::into) }) }; (from $base:ty => $xx:ident $op:tt $yy:ident) => { return Some(|_, args| { - let x = <$base>::from(args[0].$xx().unwrap()); - let y = <$base>::from(args[1].$yy().unwrap()); + let x = <$base>::from(args[0].$xx().expect(BUILTIN)); + let y = <$base>::from(args[1].$yy().expect(BUILTIN)); Ok((x $op y).into()) }) }; (from $base:ty => $xx:ident . $func:ident ( $yy:ident )) => { return Some(|_, args| { - let x = <$base>::from(args[0].$xx().unwrap()); - let y = <$base>::from(args[1].$yy().unwrap()); + let x = <$base>::from(args[0].$xx().expect(BUILTIN)); + let y = <$base>::from(args[1].$yy().expect(BUILTIN)); Ok(x.$func(y).into()) }) }; (from $base:ty => $func:ident ( $xx:ident, $yy:ident )) => { return Some(|_, args| { - let x = <$base>::from(args[0].$xx().unwrap()); - let y = <$base>::from(args[1].$yy().unwrap()); + let x = <$base>::from(args[0].$xx().expect(BUILTIN)); + let y = <$base>::from(args[1].$yy().expect(BUILTIN)); $func(x, y).map(Into::::into) }) }; @@ -175,7 +177,7 @@ pub fn get_builtin_binary_op_fn( ($x:ty, $xx:ident, $y:ty, $yy:ident) => { #[cfg(feature = "decimal")] if types_pair == (TypeId::of::<$x>(), TypeId::of::<$y>()) { - if cfg!(not(feature = "unchecked")) { + if cfg!(not(feature = "unBUILTIN")) { use crate::packages::arithmetic::decimal_functions::*; match op { @@ -222,8 +224,8 @@ pub fn get_builtin_binary_op_fn( if types_pair == (TypeId::of::(), TypeId::of::()) { #[inline(always)] fn get_s1s2(args: &FnCallArgs) -> ([char; 2], [char; 2]) { - let x = args[0].as_char().unwrap(); - let y = &*args[1].read_lock::().unwrap(); + let x = args[0].as_char().expect(BUILTIN); + let y = &*args[1].read_lock::().expect(BUILTIN); let s1 = [x, '\0']; let mut y = y.chars(); let s2 = [y.next().unwrap_or('\0'), y.next().unwrap_or('\0')]; @@ -233,8 +235,8 @@ pub fn get_builtin_binary_op_fn( match op { "+" => { return Some(|_, args| { - let x = args[0].as_char().unwrap(); - let y = &*args[1].read_lock::().unwrap(); + let x = args[0].as_char().expect(BUILTIN); + let y = &*args[1].read_lock::().expect(BUILTIN); Ok(format!("{}{}", x, y).into()) }) } @@ -251,8 +253,8 @@ pub fn get_builtin_binary_op_fn( if types_pair == (TypeId::of::(), TypeId::of::()) { #[inline(always)] fn get_s1s2(args: &FnCallArgs) -> ([char; 2], [char; 2]) { - let x = &*args[0].read_lock::().unwrap(); - let y = args[1].as_char().unwrap(); + let x = &*args[0].read_lock::().expect(BUILTIN); + let y = args[1].as_char().expect(BUILTIN); let mut x = x.chars(); let s1 = [x.next().unwrap_or('\0'), x.next().unwrap_or('\0')]; let s2 = [y, '\0']; @@ -262,15 +264,15 @@ pub fn get_builtin_binary_op_fn( match op { "+" => { return Some(|_, args| { - let x = &*args[0].read_lock::().unwrap(); - let y = args[1].as_char().unwrap(); + let x = &*args[0].read_lock::().expect(BUILTIN); + let y = args[1].as_char().expect(BUILTIN); Ok((x + y).into()) }) } "-" => { return Some(|_, args| { - let x = &*args[0].read_lock::().unwrap(); - let y = args[1].as_char().unwrap(); + let x = &*args[0].read_lock::().expect(BUILTIN); + let y = args[1].as_char().expect(BUILTIN); Ok((x - y).into()) }) } @@ -282,8 +284,8 @@ pub fn get_builtin_binary_op_fn( "<=" => impl_op!(get_s1s2(<=)), OP_CONTAINS => { return Some(|_, args| { - let s = &*args[0].read_lock::().unwrap(); - let c = args[1].as_char().unwrap(); + let s = &*args[0].read_lock::().expect(BUILTIN); + let c = args[1].as_char().expect(BUILTIN); Ok((s.contains(c)).into()) }) } @@ -314,7 +316,7 @@ pub fn get_builtin_binary_op_fn( // Beyond here, type1 == type2 if type1 == TypeId::of::() { - if cfg!(not(feature = "unchecked")) { + if cfg!(not(feature = "unBUILTIN")) { use crate::packages::arithmetic::arith_basic::INT::functions::*; match op { @@ -383,8 +385,8 @@ pub fn get_builtin_binary_op_fn( "<=" => impl_op!(ImmutableString <= ImmutableString), OP_CONTAINS => { return Some(|_, args| { - let s1 = &*args[0].read_lock::().unwrap(); - let s2 = &*args[1].read_lock::().unwrap(); + let s1 = &*args[0].read_lock::().expect(BUILTIN); + let s2 = &*args[1].read_lock::().expect(BUILTIN); Ok((s1.contains(s2.as_str())).into()) }) } @@ -396,8 +398,8 @@ pub fn get_builtin_binary_op_fn( match op { "+" => { return Some(|_, args| { - let x = args[0].as_char().unwrap(); - let y = args[1].as_char().unwrap(); + let x = args[0].as_char().expect(BUILTIN); + let y = args[1].as_char().expect(BUILTIN); Ok(format!("{}{}", x, y).into()) }) } @@ -440,55 +442,55 @@ pub fn get_builtin_op_assignment_fn( macro_rules! impl_op { ($x:ty = x $op:tt $yy:ident) => { return Some(|_, args| { - let x = args[0].$yy().unwrap(); - let y = args[1].$yy().unwrap() as $x; - Ok((*args[0].write_lock::<$x>().unwrap() = x $op y).into()) + let x = args[0].$yy().expect(BUILTIN); + let y = args[1].$yy().expect(BUILTIN) as $x; + Ok((*args[0].write_lock::<$x>().expect(BUILTIN) = x $op y).into()) }) }; ($x:ident $op:tt $yy:ident) => { return Some(|_, args| { - let y = args[1].$yy().unwrap() as $x; - Ok((*args[0].write_lock::<$x>().unwrap() $op y).into()) + let y = args[1].$yy().expect(BUILTIN) as $x; + Ok((*args[0].write_lock::<$x>().expect(BUILTIN) $op y).into()) }) }; ($x:ident $op:tt $yy:ident as $yyy:ty) => { return Some(|_, args| { - let y = args[1].$yy().unwrap() as $yyy; - Ok((*args[0].write_lock::<$x>().unwrap() $op y).into()) + let y = args[1].$yy().expect(BUILTIN) as $yyy; + Ok((*args[0].write_lock::<$x>().expect(BUILTIN) $op y).into()) }) }; ($x:ty => $xx:ident . $func:ident ( $yy:ident as $yyy:ty )) => { return Some(|_, args| { - let x = args[0].$xx().unwrap(); - let y = args[1].$yy().unwrap() as $x; - Ok((*args[0].write_lock::<$x>().unwrap() = x.$func(y as $yyy)).into()) + let x = args[0].$xx().expect(BUILTIN); + let y = args[1].$yy().expect(BUILTIN) as $x; + Ok((*args[0].write_lock::<$x>().expect(BUILTIN) = x.$func(y as $yyy)).into()) }) }; ($x:ty => $func:ident ( $xx:ident, $yy:ident )) => { return Some(|_, args| { - let x = args[0].$xx().unwrap(); - let y = args[1].$yy().unwrap() as $x; - Ok((*args[0].write_lock().unwrap() = $func(x, y)?).into()) + let x = args[0].$xx().expect(BUILTIN); + let y = args[1].$yy().expect(BUILTIN) as $x; + Ok((*args[0].write_lock().expect(BUILTIN) = $func(x, y)?).into()) }) }; (from $x:ident $op:tt $yy:ident) => { return Some(|_, args| { - let y = <$x>::from(args[1].$yy().unwrap()); - Ok((*args[0].write_lock::<$x>().unwrap() $op y).into()) + let y = <$x>::from(args[1].$yy().expect(BUILTIN)); + Ok((*args[0].write_lock::<$x>().expect(BUILTIN) $op y).into()) }) }; (from $x:ty => $xx:ident . $func:ident ( $yy:ident )) => { return Some(|_, args| { - let x = args[0].$xx().unwrap(); - let y = <$x>::from(args[1].$yy().unwrap()); - Ok((*args[0].write_lock::<$x>().unwrap() = x.$func(y)).into()) + let x = args[0].$xx().expect(BUILTIN); + let y = <$x>::from(args[1].$yy().expect(BUILTIN)); + Ok((*args[0].write_lock::<$x>().expect(BUILTIN) = x.$func(y)).into()) }) }; (from $x:ty => $func:ident ( $xx:ident, $yy:ident )) => { return Some(|_, args| { - let x = args[0].$xx().unwrap(); - let y = <$x>::from(args[1].$yy().unwrap()); - Ok((*args[0].write_lock().unwrap() = $func(x, y)?).into()) + let x = args[0].$xx().expect(BUILTIN); + let y = <$x>::from(args[1].$yy().expect(BUILTIN)); + Ok((*args[0].write_lock().expect(BUILTIN) = $func(x, y)?).into()) }) }; } @@ -517,7 +519,7 @@ pub fn get_builtin_op_assignment_fn( ($x:ident, $xx:ident, $y:ty, $yy:ident) => { #[cfg(feature = "decimal")] if types_pair == (TypeId::of::<$x>(), TypeId::of::<$y>()) { - if cfg!(not(feature = "unchecked")) { + if cfg!(not(feature = "unBUILTIN")) { use crate::packages::arithmetic::decimal_functions::*; match op { @@ -562,10 +564,15 @@ pub fn get_builtin_op_assignment_fn( match op { "+=" => { return Some(|_, args| { - let mut ch = args[0].as_char().unwrap().to_string(); - ch.push_str(args[1].read_lock::().unwrap().as_str()); + let mut ch = args[0].as_char().expect(BUILTIN).to_string(); + ch.push_str( + args[1] + .read_lock::() + .expect(BUILTIN) + .as_str(), + ); - let mut x = args[0].write_lock::().unwrap(); + let mut x = args[0].write_lock::().expect(BUILTIN); Ok((*x = ch.into()).into()) }) } @@ -580,7 +587,7 @@ pub fn get_builtin_op_assignment_fn( // Beyond here, type1 == type2 if type1 == TypeId::of::() { - if cfg!(not(feature = "unchecked")) { + if cfg!(not(feature = "unBUILTIN")) { use crate::packages::arithmetic::arith_basic::INT::functions::*; match op { @@ -628,8 +635,8 @@ pub fn get_builtin_op_assignment_fn( match op { "+=" => { return Some(|_, args| { - let y = args[1].as_char().unwrap(); - let mut x = args[0].write_lock::().unwrap(); + let y = args[1].as_char().expect(BUILTIN); + let mut x = args[0].write_lock::().expect(BUILTIN); Ok((*x = format!("{}{}", *x, y).into()).into()) }) } @@ -641,17 +648,17 @@ pub fn get_builtin_op_assignment_fn( match op { "+=" => { return Some(|_, args| { - let (first, second) = args.split_first_mut().unwrap(); - let mut x = first.write_lock::().unwrap(); - let y = &*second[0].read_lock::().unwrap(); + let (first, second) = args.split_first_mut().expect(BUILTIN); + let mut x = first.write_lock::().expect(BUILTIN); + let y = &*second[0].read_lock::().expect(BUILTIN); Ok((*x += y).into()) }) } "-=" => { return Some(|_, args| { - let (first, second) = args.split_first_mut().unwrap(); - let mut x = first.write_lock::().unwrap(); - let y = &*second[0].read_lock::().unwrap(); + let (first, second) = args.split_first_mut().expect(BUILTIN); + let mut x = first.write_lock::().expect(BUILTIN); + let y = &*second[0].read_lock::().expect(BUILTIN); Ok((*x -= y).into()) }) } diff --git a/src/fn_call.rs b/src/fn_call.rs index 0c087b2e..666add57 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -24,7 +24,7 @@ use std::prelude::v1::*; use std::{ any::{type_name, TypeId}, convert::TryFrom, - iter::{empty, once}, + iter::once, mem, }; @@ -49,7 +49,12 @@ impl<'a> ArgBackup<'a> { /// /// This method blindly casts a reference to another lifetime, which saves allocation and string cloning. /// - /// If `restore_first_arg` is called before the end of the scope, the shorter lifetime will not leak. + /// As long as `restore_first_arg` is called before the end of the scope, the shorter lifetime + /// will not leak. + /// + /// # Panics + /// + /// Panics when `args` is empty. #[inline(always)] fn change_first_arg_to_copy(&mut self, args: &mut FnCallArgs<'a>) { // Clone the original value. @@ -65,7 +70,7 @@ impl<'a> ArgBackup<'a> { // // We can do this here because, before the end of this scope, we'd restore the original reference // via `restore_first_arg`. Therefore this shorter lifetime does not leak. - self.orig_mut = Some(mem::replace(args.get_mut(0).unwrap(), unsafe { + self.orig_mut = Some(mem::replace(&mut args[0], unsafe { mem::transmute(&mut self.value_copy) })); } @@ -77,9 +82,7 @@ impl<'a> ArgBackup<'a> { /// the current scope. Otherwise it is undefined behavior as the shorter lifetime will leak. #[inline(always)] fn restore_first_arg(mut self, args: &mut FnCallArgs<'a>) { - if let Some(this_pointer) = self.orig_mut.take() { - args[0] = this_pointer; - } + self.orig_mut.take().map(|p| args[0] = p); } } @@ -162,12 +165,10 @@ impl Engine { allow_dynamic: bool, is_op_assignment: bool, ) -> &'s Option> { - let mut hash = if let Some(ref args) = args { + let mut hash = args.as_ref().map_or(hash_script, |args| { let hash_params = calc_fn_params_hash(args.iter().map(|a| a.type_id())); combine_hashes(hash_script, hash_params) - } else { - hash_script - }; + }); &*state .fn_resolution_cache_mut() @@ -239,7 +240,8 @@ impl Engine { FnResolutionCacheEntry { func, source: None } }) } else { - let (first, second) = args.split_first().unwrap(); + let (first, second) = args.split_first() + .expect("never fails because an op-assignment must have two arguments"); get_builtin_op_assignment_fn(fn_name, *first, second[0]).map( |f| { @@ -257,7 +259,9 @@ impl Engine { // Try all permutations with `Dynamic` wildcards None => { let hash_params = calc_fn_params_hash( - args.as_ref().unwrap().iter().enumerate().map(|(i, a)| { + args.as_ref().expect("never fails because there are no permutations if there are no arguments") + .iter().enumerate().map(|(i, a)| + { let mask = 1usize << (num_args - i - 1); if bitmask & mask != 0 { // Replace with `Dynamic` @@ -320,7 +324,7 @@ impl Engine { let mut backup: Option = None; if is_ref && func.is_pure() && !args.is_empty() { backup = Some(Default::default()); - backup.as_mut().unwrap().change_first_arg_to_copy(args); + backup.as_mut().map(|bk| bk.change_first_arg_to_copy(args)); } // Run external function @@ -336,9 +340,7 @@ impl Engine { }; // Restore the original reference - if let Some(backup) = backup { - backup.restore_first_arg(args); - } + backup.map(|bk| bk.restore_first_arg(args)); let result = result.map_err(|err| err.fill_position(pos))?; @@ -371,32 +373,24 @@ impl Engine { match fn_name { // index getter function not found? - #[cfg(not(feature = "no_index"))] + #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] crate::engine::FN_IDX_GET => { assert!(args.len() == 2); - EvalAltResult::ErrorFunctionNotFound( - format!( - "{} [{}]", - self.map_type_name(args[0].type_name()), - self.map_type_name(args[1].type_name()), - ), + EvalAltResult::ErrorIndexingType( + self.map_type_name(args[0].type_name()).to_string(), pos, ) .into() } // index setter function not found? - #[cfg(not(feature = "no_index"))] + #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] crate::engine::FN_IDX_SET => { assert!(args.len() == 3); - EvalAltResult::ErrorFunctionNotFound( - format!( - "{} [{}]=", - self.map_type_name(args[0].type_name()), - self.map_type_name(args[1].type_name()), - ), + EvalAltResult::ErrorIndexingType( + self.map_type_name(args[0].type_name()).to_string(), pos, ) .into() @@ -654,14 +648,18 @@ impl Engine { crate::engine::KEYWORD_IS_DEF_FN if args.len() == 2 && args[0].is::() && args[1].is::() => { - let fn_name = &*args[0].read_lock::().unwrap(); - let num_params = args[1].as_int().unwrap(); + let fn_name = &*args[0] + .read_lock::() + .expect("never fails because `args[0]` is `FnPtr`"); + let num_params = args[1] + .as_int() + .expect("never fails because `args[1]` is `INT`"); return Ok(( if num_params < 0 { Dynamic::FALSE } else { - let hash_script = calc_fn_hash(empty(), fn_name, num_params as usize); + let hash_script = calc_fn_hash(fn_name, num_params as usize); self.has_script_fn(Some(mods), state, lib, hash_script) .into() }, @@ -712,11 +710,7 @@ impl Engine { // Scripted function call? #[cfg(not(feature = "no_function"))] - let hash_script = if hash.is_native_only() { - None - } else { - Some(hash.script_hash()) - }; + let hash_script = hash.script_hash(); #[cfg(not(feature = "no_function"))] if let Some(f) = hash_script.and_then(|hash| { @@ -737,21 +731,23 @@ impl Engine { // Move captured variables into scope #[cfg(not(feature = "no_closure"))] - if let Some(captured) = _capture_scope { - if !func.externals.is_empty() { + if !func.externals.is_empty() { + _capture_scope.map(|captured| { captured .into_iter() .filter(|(name, _, _)| func.externals.contains(name.as_ref())) .for_each(|(name, value, _)| { // Consume the scope values. scope.push_dynamic(name, value); - }); - } + }) + }); } let result = if _is_method { // Method call of script function - map first argument to `this` - let (first, rest) = args.split_first_mut().unwrap(); + let (first, rest) = args + .split_first_mut() + .expect("never fails because a method call always has a first parameter"); let orig_source = state.source.take(); state.source = source; @@ -780,7 +776,7 @@ impl Engine { let mut backup: Option = None; if is_ref && !args.is_empty() { backup = Some(Default::default()); - backup.as_mut().unwrap().change_first_arg_to_copy(args); + backup.as_mut().map(|bk| bk.change_first_arg_to_copy(args)); } let orig_source = state.source.take(); @@ -795,9 +791,7 @@ impl Engine { state.source = orig_source; // Restore the original reference - if let Some(backup) = backup { - backup.restore_first_arg(args); - } + backup.map(|bk| bk.restore_first_arg(args)); result? }; @@ -914,12 +908,14 @@ impl Engine { let (result, updated) = match fn_name { KEYWORD_FN_PTR_CALL if obj.is::() => { // FnPtr call - let fn_ptr = obj.read_lock::().unwrap(); + let fn_ptr = obj + .read_lock::() + .expect("never fails because `obj` is `FnPtr`"); // Redirect function name let fn_name = fn_ptr.fn_name(); let args_len = call_args.len() + fn_ptr.curry().len(); // Recalculate hashes - let new_hash = FnCallHashes::from_script(calc_fn_hash(empty(), fn_name, args_len)); + let new_hash = FnCallHashes::from_script(calc_fn_hash(fn_name, args_len)); // Arguments are passed as-is, adding the curried arguments let mut curry = fn_ptr.curry().iter().cloned().collect::>(); let mut args = curry @@ -955,8 +951,8 @@ impl Engine { let args_len = call_args.len() + fn_ptr.curry().len(); // Recalculate hash let new_hash = FnCallHashes::from_script_and_native( - calc_fn_hash(empty(), fn_name, args_len), - calc_fn_hash(empty(), fn_name, args_len + 1), + calc_fn_hash(fn_name, args_len), + calc_fn_hash(fn_name, args_len + 1), ); // Replace the first argument with the object pointer, adding the curried arguments let mut curry = fn_ptr.curry().iter().cloned().collect::>(); @@ -978,7 +974,9 @@ impl Engine { )); } - let fn_ptr = obj.read_lock::().unwrap(); + let fn_ptr = obj + .read_lock::() + .expect("never fails because `obj` is `FnPtr`"); // Curry call Ok(( @@ -1029,8 +1027,8 @@ impl Engine { }); // Recalculate the hash based on the new function name and new arguments hash = FnCallHashes::from_script_and_native( - calc_fn_hash(empty(), fn_name, call_args.len()), - calc_fn_hash(empty(), fn_name, call_args.len() + 1), + calc_fn_hash(fn_name, call_args.len()), + calc_fn_hash(fn_name, call_args.len() + 1), ); } } @@ -1049,7 +1047,9 @@ impl Engine { // Propagate the changed value back to the source if necessary if updated { - target.propagate_changed_value(); + target + .propagate_changed_value() + .map_err(|err| err.fill_position(pos))?; } Ok((result, updated)) @@ -1115,9 +1115,9 @@ impl Engine { // Recalculate hash let args_len = total_args + curry.len(); hashes = if !hashes.is_native_only() { - FnCallHashes::from_script(calc_fn_hash(empty(), name, args_len)) + FnCallHashes::from_script(calc_fn_hash(name, args_len)) } else { - FnCallHashes::from_native(calc_fn_hash(empty(), name, args_len)) + FnCallHashes::from_native(calc_fn_hash(name, args_len)) }; } // Handle Fn() @@ -1214,7 +1214,7 @@ impl Engine { return Ok(if num_params < 0 { Dynamic::FALSE } else { - let hash_script = calc_fn_hash(empty(), &fn_name, num_params as usize); + let hash_script = calc_fn_hash(&fn_name, num_params as usize); self.has_script_fn(Some(mods), state, lib, hash_script) .into() }); @@ -1332,9 +1332,8 @@ impl Engine { } else { // Turn it into a method call only if the object is not shared and not a simple value is_ref = true; - once(target.take_ref().unwrap()) - .chain(arg_values.iter_mut()) - .collect() + let obj_ref = target.take_ref().expect("never fails because `target` is a reference if it is not a value and not shared"); + once(obj_ref).chain(arg_values.iter_mut()).collect() }; } else { // func(..., ...) @@ -1365,7 +1364,7 @@ impl Engine { state: &mut State, lib: &[&Module], this_ptr: &mut Option<&mut Dynamic>, - namespace: Option<&NamespaceRef>, + namespace: &NamespaceRef, fn_name: &str, args_expr: &[Expr], literal_args: &[(Dynamic, Position)], @@ -1373,7 +1372,6 @@ impl Engine { pos: Position, level: usize, ) -> RhaiResult { - let namespace = namespace.unwrap(); let mut arg_values: StaticVec<_>; let mut first_arg_value = None; let mut args: StaticVec<_>; @@ -1418,11 +1416,12 @@ impl Engine { arg_values[0] = target.take_or_clone().flatten(); args = arg_values.iter_mut().collect(); } else { - let (first, rest) = arg_values.split_first_mut().unwrap(); + let (first, rest) = arg_values + .split_first_mut() + .expect("never fails because the arguments list is not empty"); first_arg_value = Some(first); - args = once(target.take_ref().unwrap()) - .chain(rest.iter_mut()) - .collect(); + let obj_ref = target.take_ref().expect("never fails because `target` is a reference if it is not a value and not shared"); + args = once(obj_ref).chain(rest.iter_mut()).collect(); } } else { // func(..., ...) or func(mod::x, ...) @@ -1459,12 +1458,11 @@ impl Engine { }; // Clone first argument if the function is not a method after-all - if let Some(first) = first_arg_value { - if !func.map(|f| f.is_method()).unwrap_or(true) { - let first_val = args[0].clone(); + if !func.map(|f| f.is_method()).unwrap_or(true) { + first_arg_value.map(|first| { + *first = args[0].clone(); args[0] = first; - *args[0] = first_val; - } + }); } match func { diff --git a/src/fn_native.rs b/src/fn_native.rs index 5996d6db..0b22fe9b 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -13,7 +13,7 @@ use std::prelude::v1::*; use std::{ convert::{TryFrom, TryInto}, fmt, - iter::{empty, once}, + iter::once, mem, }; @@ -202,11 +202,11 @@ impl<'a> NativeCallContext<'a> { let hash = if is_method { FnCallHashes::from_script_and_native( - calc_fn_hash(empty(), fn_name, args.len() - 1), - calc_fn_hash(empty(), fn_name, args.len()), + calc_fn_hash(fn_name, args.len() - 1), + calc_fn_hash(fn_name, args.len()), ) } else { - FnCallHashes::from_script(calc_fn_hash(empty(), fn_name, args.len())) + FnCallHashes::from_script(calc_fn_hash(fn_name, args.len())) }; self.engine() @@ -253,7 +253,9 @@ pub fn shared_try_take(value: Shared) -> Result> { /// Panics if the resource is shared (i.e. has other outstanding references). #[inline(always)] pub fn shared_take(value: Shared) -> T { - shared_try_take(value).map_err(|_| ()).unwrap() + shared_try_take(value) + .ok() + .expect("resource should have no outstanding references") } /// Arguments to a function call, which is a list of [`&mut Dynamic`][Dynamic]. diff --git a/src/fn_register.rs b/src/fn_register.rs index e7a4c6b7..80cfb2cf 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -32,7 +32,8 @@ pub struct Mut(T); #[inline(always)] pub fn by_ref(data: &mut Dynamic) -> DynamicWriteLock { // Directly cast the &mut Dynamic into DynamicWriteLock to access the underlying data. - data.write_lock::().unwrap() + data.write_lock::() + .expect("never fails because the type was checked") } /// Dereference into value. @@ -43,12 +44,15 @@ pub fn by_value(data: &mut Dynamic) -> T { data.flatten_in_place(); let ref_str = data .as_str_ref() - .expect("argument passed by value should not be shared"); + .expect("never fails because argument passed by value should not be shared"); let ref_t = unsafe { mem::transmute::<_, &T>(&ref_str) }; ref_t.clone() } else if TypeId::of::() == TypeId::of::() { // If T is `String`, data must be `ImmutableString`, so map directly to it - unsafe_try_cast(mem::take(data).take_string().unwrap()).unwrap() + let value = mem::take(data) + .take_string() + .expect("never fails because the type was checked"); + unsafe_try_cast(value).expect("never fails because the type was checked") } else { // We consume the argument and then replace it with () - the argument is not supposed to be used again. // This way, we avoid having to clone the argument again, because it is already a clone when passed here. @@ -118,7 +122,7 @@ macro_rules! def_register { // The arguments are assumed to be of the correct number and types! let mut _drain = args.iter_mut(); - $($let $par = ($clone)(_drain.next().unwrap()); )* + $($let $par = ($clone)(_drain.next().expect("never fails because arguments list is fixed")); )* // Call the function with each argument value let r = self($($arg),*); @@ -146,7 +150,7 @@ macro_rules! def_register { // The arguments are assumed to be of the correct number and types! let mut _drain = args.iter_mut(); - $($let $par = ($clone)(_drain.next().unwrap()); )* + $($let $par = ($clone)(_drain.next().expect("never fails because arguments list is fixed")); )* // Call the function with each argument value let r = self(ctx, $($arg),*); @@ -174,7 +178,7 @@ macro_rules! def_register { // The arguments are assumed to be of the correct number and types! let mut _drain = args.iter_mut(); - $($let $par = ($clone)(_drain.next().unwrap()); )* + $($let $par = ($clone)(_drain.next().expect("never fails because arguments list is fixed")); )* // Call the function with each argument value self($($arg),*).map(Dynamic::from) @@ -199,7 +203,7 @@ macro_rules! def_register { // The arguments are assumed to be of the correct number and types! let mut _drain = args.iter_mut(); - $($let $par = ($clone)(_drain.next().unwrap()); )* + $($let $par = ($clone)(_drain.next().expect("never fails because arguments list is fixed")); )* // Call the function with each argument value self(ctx, $($arg),*).map(Dynamic::from) diff --git a/src/lib.rs b/src/lib.rs index 30b9faa9..2db92c3e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -169,7 +169,7 @@ pub use fn_native::Shared; #[cfg(not(feature = "no_closure"))] use fn_native::Locked; -pub(crate) use utils::{calc_fn_hash, calc_fn_params_hash, combine_hashes}; +pub(crate) use utils::{calc_fn_hash, calc_fn_params_hash, calc_qualified_fn_hash, combine_hashes}; pub use rhai_codegen::*; diff --git a/src/module/mod.rs b/src/module/mod.rs index 16c93374..c9f9966f 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -7,8 +7,8 @@ use crate::fn_register::RegisterNativeFunction; use crate::token::Token; use crate::utils::IdentifierBuilder; use crate::{ - calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, EvalAltResult, Identifier, - ImmutableString, NativeCallContext, Position, Shared, StaticVec, + calc_fn_params_hash, calc_qualified_fn_hash, combine_hashes, Dynamic, EvalAltResult, + Identifier, ImmutableString, NativeCallContext, Position, Shared, StaticVec, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -24,7 +24,6 @@ use std::{ #[cfg(not(feature = "no_index"))] use crate::Array; -#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_object"))] use crate::Map; @@ -117,7 +116,7 @@ fn calc_native_fn_hash<'a>( fn_name: impl AsRef, params: &[TypeId], ) -> u64 { - let hash_script = calc_fn_hash(modules, fn_name, params.len()); + let hash_script = calc_qualified_fn_hash(modules, fn_name, params.len()); let hash_params = calc_fn_params_hash(params.iter().cloned()); combine_hashes(hash_script, hash_params) } @@ -156,20 +155,7 @@ pub struct Module { impl Default for Module { #[inline(always)] fn default() -> Self { - Self { - id: None, - internal: false, - modules: Default::default(), - variables: Default::default(), - all_variables: Default::default(), - functions: Default::default(), - all_functions: Default::default(), - type_iterators: Default::default(), - all_type_iterators: Default::default(), - indexed: true, - contains_indexed_global_functions: false, - identifiers: Default::default(), - } + Self::new() } } @@ -177,9 +163,7 @@ impl fmt::Debug for Module { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut d = f.debug_struct("Module"); - if let Some(ref id) = self.id { - d.field("id", id); - } + self.id.as_ref().map(|id| d.field("id", id)); if !self.modules.is_empty() { d.field( @@ -257,7 +241,20 @@ impl Module { /// ``` #[inline(always)] pub fn new() -> Self { - Default::default() + Self { + id: None, + internal: false, + modules: Default::default(), + variables: Default::default(), + all_variables: Default::default(), + functions: Default::default(), + all_functions: Default::default(), + type_iterators: Default::default(), + all_type_iterators: Default::default(), + indexed: true, + contains_indexed_global_functions: false, + identifiers: Default::default(), + } } /// Get the ID of the [`Module`], if any. @@ -441,7 +438,7 @@ impl Module { let value = Dynamic::from(value); if self.indexed { - let hash_var = crate::calc_fn_hash(once(""), &ident, 0); + let hash_var = crate::calc_qualified_fn_hash(once(""), &ident, 0); self.all_variables.insert(hash_var, value.clone()); } self.variables.insert(ident, value); @@ -467,7 +464,7 @@ impl Module { // None + function name + number of arguments. let num_params = fn_def.params.len(); - let hash_script = crate::calc_fn_hash(empty(), &fn_def.name, num_params); + let hash_script = crate::calc_fn_hash(&fn_def.name, num_params); let mut param_names = fn_def.params.clone(); param_names.push("Dynamic".into()); self.functions.insert( @@ -622,9 +619,10 @@ impl Module { .map(|&name| self.identifiers.get(name)) .collect(); - if let Some(f) = self.functions.get_mut(&hash_fn) { - f.param_names = param_names; - } + self.functions + .get_mut(&hash_fn) + .map(|f| f.param_names = param_names); + self } @@ -956,7 +954,7 @@ impl Module { /// }); /// assert!(module.contains_fn(hash)); /// ``` - #[cfg(not(feature = "no_index"))] + #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[inline(always)] pub fn set_indexer_get_fn(&mut self, func: F) -> u64 where @@ -966,6 +964,7 @@ impl Module { F: RegisterNativeFunction>>, F: Fn(&mut A, B) -> Result> + SendSync + 'static, { + #[cfg(not(feature = "no_index"))] if TypeId::of::() == TypeId::of::() { panic!("Cannot register indexer for arrays."); } @@ -1016,7 +1015,7 @@ impl Module { /// }); /// assert!(module.contains_fn(hash)); /// ``` - #[cfg(not(feature = "no_index"))] + #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[inline(always)] pub fn set_indexer_set_fn(&mut self, func: F) -> u64 where @@ -1026,6 +1025,7 @@ impl Module { F: RegisterNativeFunction>>, F: Fn(&mut A, B, C) -> Result<(), Box> + SendSync + 'static, { + #[cfg(not(feature = "no_index"))] if TypeId::of::() == TypeId::of::() { panic!("Cannot register indexer for arrays."); } @@ -1082,7 +1082,7 @@ impl Module { /// assert!(module.contains_fn(hash_get)); /// assert!(module.contains_fn(hash_set)); /// ``` - #[cfg(not(feature = "no_index"))] + #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[inline(always)] pub fn set_indexer_get_set_fn( &mut self, @@ -1414,7 +1414,10 @@ impl Module { match aliases.len() { 0 => (), 1 => { - module.set_var(aliases.pop().unwrap(), value); + let alias = aliases + .pop() + .expect("never fails because the list has one item"); + module.set_var(alias, value); } _ => aliases.into_iter().for_each(|alias| { module.set_var(alias, value.clone()); @@ -1490,7 +1493,7 @@ impl Module { // Index all variables module.variables.iter().for_each(|(var_name, value)| { - let hash_var = crate::calc_fn_hash(path.iter().map(|&v| v), var_name, 0); + let hash_var = crate::calc_qualified_fn_hash(path.iter().map(|&v| v), var_name, 0); variables.insert(hash_var, value.clone()); }); @@ -1520,8 +1523,11 @@ impl Module { calc_native_fn_hash(path.iter().cloned(), f.name.as_str(), &f.param_types); functions.insert(hash_qualified_fn, f.func.clone()); } else if cfg!(not(feature = "no_function")) { - let hash_qualified_script = - crate::calc_fn_hash(path.iter().cloned(), f.name.as_str(), f.params); + let hash_qualified_script = crate::calc_qualified_fn_hash( + path.iter().cloned(), + f.name.as_str(), + f.params, + ); functions.insert(hash_qualified_script, f.func.clone()); } }); diff --git a/src/module/resolvers/dummy.rs b/src/module/resolvers/dummy.rs index 3feb6852..15c706f2 100644 --- a/src/module/resolvers/dummy.rs +++ b/src/module/resolvers/dummy.rs @@ -31,8 +31,8 @@ impl DummyModuleResolver { /// engine.set_module_resolver(resolver); /// ``` #[inline(always)] - pub fn new() -> Self { - Default::default() + pub const fn new() -> Self { + Self } } diff --git a/src/optimize.rs b/src/optimize.rs index be02071c..52c4926e 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -4,7 +4,6 @@ use crate::ast::{Expr, OpAssignment, Stmt}; use crate::dynamic::AccessMode; use crate::engine::{KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, KEYWORD_TYPE_OF}; use crate::fn_builtin::get_builtin_binary_op_fn; -use crate::parser::map_dynamic_to_expr; use crate::token::Token; use crate::utils::get_hasher; use crate::{ @@ -16,7 +15,6 @@ use std::prelude::v1::*; use std::{ any::TypeId, hash::{Hash, Hasher}, - iter::empty, mem, }; @@ -32,24 +30,14 @@ pub enum OptimizationLevel { Full, } -impl OptimizationLevel { - /// Is the `OptimizationLevel` [`None`][OptimizationLevel::None]? - #[allow(dead_code)] +impl Default for OptimizationLevel { #[inline(always)] - pub fn is_none(self) -> bool { - self == Self::None - } - /// Is the `OptimizationLevel` [`Simple`][OptimizationLevel::Simple]? - #[allow(dead_code)] - #[inline(always)] - pub fn is_simple(self) -> bool { - self == Self::Simple - } - /// Is the `OptimizationLevel` [`Full`][OptimizationLevel::Full]? - #[allow(dead_code)] - #[inline(always)] - pub fn is_full(self) -> bool { - self == Self::Full + fn default() -> Self { + if cfg!(feature = "no_optimize") { + Self::None + } else { + Self::Simple + } } } @@ -59,7 +47,7 @@ struct State<'a> { /// Has the [`AST`] been changed during this pass? changed: bool, /// Collection of constants to use for eager function evaluations. - variables: Vec<(String, AccessMode, Expr)>, + variables: Vec<(String, AccessMode, Option)>, /// Activate constants propagation? propagate_constants: bool, /// An [`Engine`] instance for eager function evaluation. @@ -109,21 +97,21 @@ impl<'a> State<'a> { } /// Add a new constant to the list. #[inline(always)] - pub fn push_var(&mut self, name: &str, access: AccessMode, value: Expr) { + pub fn push_var(&mut self, name: &str, access: AccessMode, value: Option) { self.variables.push((name.into(), access, value)) } /// Look up a constant from the list. #[inline] - pub fn find_constant(&self, name: &str) -> Option<&Expr> { + pub fn find_constant(&self, name: &str) -> Option<&Dynamic> { if !self.propagate_constants { return None; } - self.variables.iter().rev().find_map(|(n, access, expr)| { + self.variables.iter().rev().find_map(|(n, access, value)| { if n == name { match access { AccessMode::ReadWrite => None, - AccessMode::ReadOnly => Some(expr), + AccessMode::ReadOnly => value.as_ref(), } } else { None @@ -158,7 +146,7 @@ fn call_fn_with_constant_arguments( &mut Default::default(), state.lib, fn_name, - calc_fn_hash(empty(), fn_name, arg_values.len()), + calc_fn_hash(fn_name, arg_values.len()), arg_values.iter_mut().collect::>().as_mut(), false, false, @@ -217,13 +205,17 @@ fn optimize_stmt_block( optimize_expr(value_expr, state); if value_expr.is_constant() { - state.push_var(&x.name, AccessMode::ReadOnly, value_expr.clone()); + state.push_var( + &x.name, + AccessMode::ReadOnly, + value_expr.get_constant_value(), + ); } } // Add variables into the state Stmt::Let(value_expr, x, _, _) => { optimize_expr(value_expr, state); - state.push_var(&x.name, AccessMode::ReadWrite, Expr::Unit(x.pos)); + state.push_var(&x.name, AccessMode::ReadWrite, None); } // Optimize the statement _ => optimize_stmt(stmt, state, preserve_result), @@ -512,12 +504,13 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) { Stmt::Switch(match_expr, x, _) => { optimize_expr(match_expr, state); x.0.values_mut().for_each(|block| { - let condition = if let Some(mut condition) = mem::take(&mut block.0) { - optimize_expr(&mut condition, state); - condition - } else { - Expr::Unit(Position::NONE) - }; + let condition = mem::take(&mut block.0).map_or_else( + || Expr::Unit(Position::NONE), + |mut condition| { + optimize_expr(&mut condition, state); + condition + }, + ); match condition { Expr::Unit(_) | Expr::BoolConstant(true, _) => (), @@ -703,7 +696,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) { Expr::Dot(x, _) => match (&mut x.lhs, &mut x.rhs) { // map.string (Expr::Map(m, pos), Expr::Property(p)) if m.0.iter().all(|(_, x)| x.is_pure()) => { - let prop = &p.2.name; + let prop = p.2.0.as_str(); // Map literal where everything is pure - promote the indexed item. // All other items can be thrown away. state.set_dirty(); @@ -925,15 +918,16 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) { // Search for overloaded operators (can override built-in). if !has_native_fn(state, x.hashes.native_hash(), arg_types.as_ref()) { - if let Some(result) = get_builtin_binary_op_fn(x.name.as_ref(), &arg_values[0], &arg_values[1]) - .and_then(|f| { - let ctx = (state.engine, x.name.as_ref(), state.lib).into(); - let (first, second) = arg_values.split_first_mut().unwrap(); - (f)(ctx, &mut [ first, &mut second[0] ]).ok() - }) - .and_then(|result| map_dynamic_to_expr(result, *pos)) + if let Some(mut result) = get_builtin_binary_op_fn(x.name.as_ref(), &arg_values[0], &arg_values[1]) + .and_then(|f| { + let ctx = (state.engine, x.name.as_ref(), state.lib).into(); + let (first, second) = arg_values.split_first_mut().unwrap(); + (f)(ctx, &mut [ first, &mut second[0] ]).ok() + }) + .map(Expr::from) { state.set_dirty(); + result.set_position(*pos); *expr = result; return; } @@ -977,18 +971,19 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) { "" }; - if let Some(result) = call_fn_with_constant_arguments(&state, x.name.as_ref(), &mut arg_values) - .or_else(|| { - if !arg_for_type_of.is_empty() { - // Handle `type_of()` - Some(arg_for_type_of.to_string().into()) - } else { - None - } - }) - .and_then(|result| map_dynamic_to_expr(result, *pos)) + if let Some(mut result) = call_fn_with_constant_arguments(&state, x.name.as_ref(), &mut arg_values) + .or_else(|| { + if !arg_for_type_of.is_empty() { + // Handle `type_of()` + Some(arg_for_type_of.to_string().into()) + } else { + None + } + }) + .map(Expr::from) { state.set_dirty(); + result.set_position(*pos); *expr = result; return; } @@ -1014,12 +1009,11 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) { // constant-name Expr::Variable(_, pos, x) if x.1.is_none() && state.find_constant(&x.2).is_some() => { - state.set_dirty(); - // Replace constant with value - let mut result = state.find_constant(&x.2).unwrap().clone(); - result.set_position(*pos); - *expr = result; + let pos = *pos; + *expr = Expr::from(state.find_constant(&x.2).unwrap().clone()); + expr.set_position(pos); + state.set_dirty(); } // Custom syntax @@ -1055,13 +1049,9 @@ fn optimize_top_level( // Add constants and variables from the scope scope.iter().for_each(|(name, constant, value)| { if !constant { - state.push_var(name, AccessMode::ReadWrite, Expr::Unit(Position::NONE)); + state.push_var(name, AccessMode::ReadWrite, None); } else { - state.push_var( - name, - AccessMode::ReadOnly, - Expr::DynamicConstant(Box::new(value), Position::NONE), - ); + state.push_var(name, AccessMode::ReadOnly, Some(value)); } }); @@ -1078,7 +1068,7 @@ pub fn optimize_into_ast( optimization_level: OptimizationLevel, ) -> AST { let level = if cfg!(feature = "no_optimize") { - OptimizationLevel::None + Default::default() } else { optimization_level }; @@ -1087,7 +1077,7 @@ pub fn optimize_into_ast( let lib = { let mut module = Module::new(); - if !level.is_none() { + if level != OptimizationLevel::None { // We only need the script library's signatures for optimization purposes let mut lib2 = Module::new(); diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index f17a62aa..13cbcb19 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -604,27 +604,13 @@ mod array_functions { .call_dynamic(&ctx, None, [x.clone(), y.clone()]) .ok() .and_then(|v| v.as_int().ok()) - .map(|v| { - if v > 0 { - Ordering::Greater - } else if v < 0 { - Ordering::Less - } else { - Ordering::Equal - } - }) - .unwrap_or_else(|| { - let x_type_id = x.type_id(); - let y_type_id = y.type_id(); - - if x_type_id > y_type_id { - Ordering::Greater - } else if x_type_id < y_type_id { - Ordering::Less - } else { - Ordering::Equal - } + .map(|v| match v { + v if v > 0 => Ordering::Greater, + v if v < 0 => Ordering::Less, + 0 => Ordering::Equal, + _ => unreachable!(), }) + .unwrap_or_else(|| x.type_id().cmp(&y.type_id())) }); Ok(()) diff --git a/src/packages/fn_basic.rs b/src/packages/fn_basic.rs index 63688c04..f88089c5 100644 --- a/src/packages/fn_basic.rs +++ b/src/packages/fn_basic.rs @@ -45,29 +45,31 @@ fn collect_fn_metadata(ctx: NativeCallContext) -> crate::Array { namespace: Option, f: &ScriptFnDef, ) -> Map { + const DICT: &str = "never fails because the dictionary is pre-filled with all the keys"; + let mut map = Map::new(); if let Some(ns) = namespace { - map.insert(dict.get("namespace").unwrap().clone().into(), ns.into()); + map.insert(dict.get("namespace").expect(DICT).clone().into(), ns.into()); } map.insert( - dict.get("name").unwrap().clone().into(), + dict.get("name").expect(DICT).clone().into(), f.name.clone().into(), ); map.insert( - dict.get("access").unwrap().clone().into(), + dict.get("access").expect(DICT).clone().into(), match f.access { - FnAccess::Public => dict.get("public").unwrap().clone(), - FnAccess::Private => dict.get("private").unwrap().clone(), + FnAccess::Public => dict.get("public").expect(DICT).clone(), + FnAccess::Private => dict.get("private").expect(DICT).clone(), } .into(), ); map.insert( - dict.get("is_anonymous").unwrap().clone().into(), + dict.get("is_anonymous").expect(DICT).clone().into(), f.name.starts_with(crate::engine::FN_ANONYMOUS).into(), ); map.insert( - dict.get("params").unwrap().clone().into(), + dict.get("params").expect(DICT).clone().into(), f.params .iter() .cloned() diff --git a/src/packages/math_basic.rs b/src/packages/math_basic.rs index e4d8b0b4..1e790000 100644 --- a/src/packages/math_basic.rs +++ b/src/packages/math_basic.rs @@ -311,21 +311,14 @@ mod decimal_functions { #[rhai_fn(return_raw)] pub fn sqrt(x: Decimal) -> Result> { - if cfg!(not(feature = "unchecked")) { - x.sqrt() - .ok_or_else(|| make_err(format!("Error taking the square root of {}", x,))) - } else { - Ok(x.sqrt().unwrap()) - } + x.sqrt() + .ok_or_else(|| make_err(format!("Error taking the square root of {}", x,))) } #[rhai_fn(return_raw)] pub fn exp(x: Decimal) -> Result> { if cfg!(not(feature = "unchecked")) { - if x > Decimal::from_parts(117578, 0, 0, false, 4) { - Err(make_err(format!("Exponential overflow: e ** {}", x,))) - } else { - Ok(x.exp()) - } + x.checked_exp() + .ok_or_else(|| make_err(format!("Exponential overflow: e ** {}", x,))) } else { Ok(x.exp()) } @@ -348,15 +341,15 @@ mod decimal_functions { #[rhai_fn(name = "round", return_raw)] pub fn round_dp(x: Decimal, dp: INT) -> Result> { if cfg!(not(feature = "unchecked")) { - if cfg!(not(feature = "only_i32")) && dp > (u32::MAX as INT) { - return Ok(x); - } if dp < 0 { return Err(make_err(format!( - "Decimal value {} round to a negative index: {}", - x, dp + "Invalid number of digits for rounding: {}", + dp ))); } + if cfg!(not(feature = "only_i32")) && dp > (u32::MAX as INT) { + return Ok(x); + } } Ok(x.round_dp(dp as u32)) @@ -364,15 +357,15 @@ mod decimal_functions { #[rhai_fn(return_raw)] pub fn round_up(x: Decimal, dp: INT) -> Result> { if cfg!(not(feature = "unchecked")) { - if cfg!(not(feature = "only_i32")) && dp > (u32::MAX as INT) { - return Ok(x); - } if dp < 0 { return Err(make_err(format!( - "Decimal value {} round to a negative index: {}", - x, dp + "Invalid number of digits for rounding: {}", + dp ))); } + if cfg!(not(feature = "only_i32")) && dp > (u32::MAX as INT) { + return Ok(x); + } } Ok(x.round_dp_with_strategy(dp as u32, RoundingStrategy::AwayFromZero)) @@ -380,15 +373,15 @@ mod decimal_functions { #[rhai_fn(return_raw)] pub fn round_down(x: Decimal, dp: INT) -> Result> { if cfg!(not(feature = "unchecked")) { - if cfg!(not(feature = "only_i32")) && dp > (u32::MAX as INT) { - return Ok(x); - } if dp < 0 { return Err(make_err(format!( - "Decimal value {} round to a negative index: {}", - x, dp + "Invalid number of digits for rounding: {}", + dp ))); } + if cfg!(not(feature = "only_i32")) && dp > (u32::MAX as INT) { + return Ok(x); + } } Ok(x.round_dp_with_strategy(dp as u32, RoundingStrategy::ToZero)) @@ -396,15 +389,15 @@ mod decimal_functions { #[rhai_fn(return_raw)] pub fn round_half_up(x: Decimal, dp: INT) -> Result> { if cfg!(not(feature = "unchecked")) { - if cfg!(not(feature = "only_i32")) && dp > (u32::MAX as INT) { - return Ok(x); - } if dp < 0 { return Err(make_err(format!( - "Decimal value {} round to a negative index: {}", - x, dp + "Invalid number of digits for rounding: {}", + dp ))); } + if cfg!(not(feature = "only_i32")) && dp > (u32::MAX as INT) { + return Ok(x); + } } Ok(x.round_dp_with_strategy(dp as u32, RoundingStrategy::MidpointAwayFromZero)) @@ -412,15 +405,15 @@ mod decimal_functions { #[rhai_fn(return_raw)] pub fn round_half_down(x: Decimal, dp: INT) -> Result> { if cfg!(not(feature = "unchecked")) { - if cfg!(not(feature = "only_i32")) && dp > (u32::MAX as INT) { - return Ok(x); - } if dp < 0 { return Err(make_err(format!( - "Decimal value {} round to a negative index: {}", - x, dp + "Invalid number of digits for rounding: {}", + dp ))); } + if cfg!(not(feature = "only_i32")) && dp > (u32::MAX as INT) { + return Ok(x); + } } Ok(x.round_dp_with_strategy(dp as u32, RoundingStrategy::MidpointTowardZero)) diff --git a/src/packages/string_basic.rs b/src/packages/string_basic.rs index ea02a027..be07f1e0 100644 --- a/src/packages/string_basic.rs +++ b/src/packages/string_basic.rs @@ -27,9 +27,9 @@ pub fn print_with_func( value: &mut Dynamic, ) -> crate::ImmutableString { match ctx.call_fn_dynamic_raw(fn_name, true, &mut [value]) { - Ok(result) if result.is::() => { - result.take_immutable_string().unwrap() - } + Ok(result) if result.is::() => result + .take_immutable_string() + .expect("never fails as the result is `ImmutableString`"), Ok(result) => ctx.engine().map_type_name(result.type_name()).into(), Err(_) => ctx.engine().map_type_name(value.type_name()).into(), } diff --git a/src/parser.rs b/src/parser.rs index c8a82deb..d087f518 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -15,15 +15,14 @@ use crate::token::{ }; use crate::utils::{get_hasher, IdentifierBuilder}; use crate::{ - calc_fn_hash, Dynamic, Engine, FnPtr, Identifier, LexError, ParseError, ParseErrorType, - Position, Scope, Shared, StaticVec, AST, + calc_fn_hash, calc_qualified_fn_hash, Dynamic, Engine, FnPtr, Identifier, LexError, ParseError, + ParseErrorType, Position, Scope, Shared, StaticVec, AST, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{ collections::BTreeMap, hash::{Hash, Hasher}, - iter::empty, num::{NonZeroU8, NonZeroUsize}, }; @@ -37,6 +36,8 @@ type PERR = ParseErrorType; type FunctionsLib = BTreeMap>; +const NEVER_ENDS: &str = "never fails because `TokenStream` never ends"; + /// A type that encapsulates the current state of the parser. #[derive(Debug)] pub struct ParseState<'e> { @@ -206,10 +207,8 @@ impl ParseSettings { &self, limit: Option, ) -> Result<(), ParseError> { - if let Some(limit) = limit { - if self.level > limit.get() { - return Err(PERR::ExprTooDeep.into_err(self.pos)); - } + if limit.map(|limit| self.level > limit.get()).unwrap_or(false) { + return Err(PERR::ExprTooDeep.into_err(self.pos)); } Ok(()) } @@ -225,17 +224,14 @@ impl Expr { Self::Variable(_, pos, x) if x.1.is_none() => { let ident = x.2; let getter = state.get_identifier(crate::engine::make_getter(&ident)); - let hash_get = calc_fn_hash(empty(), &getter, 1); + let hash_get = calc_fn_hash(&getter, 1); let setter = state.get_identifier(crate::engine::make_setter(&ident)); - let hash_set = calc_fn_hash(empty(), &setter, 2); + let hash_set = calc_fn_hash(&setter, 2); Self::Property(Box::new(( (getter, hash_get), (setter, hash_set), - Ident { - name: state.get_identifier(ident), - pos, - }, + (state.get_identifier(ident).into(), pos), ))) } _ => self, @@ -245,7 +241,7 @@ impl Expr { /// Consume a particular [token][Token], checking that it is the expected one. fn eat_token(input: &mut TokenStream, token: Token) -> Position { - let (t, pos) = input.next().unwrap(); + let (t, pos) = input.next().expect(NEVER_ENDS); if t != token { unreachable!( @@ -260,7 +256,7 @@ fn eat_token(input: &mut TokenStream, token: Token) -> Position { /// Match a particular [token][Token], consuming it if matched. fn match_token(input: &mut TokenStream, token: Token) -> (bool, Position) { - let (t, pos) = input.peek().unwrap(); + let (t, pos) = input.peek().expect(NEVER_ENDS); if *t == token { (true, eat_token(input, token)) } else { @@ -287,7 +283,7 @@ fn parse_paren_expr( let expr = parse_expr(input, state, lib, settings.level_up())?; - match input.next().unwrap() { + match input.next().expect(NEVER_ENDS) { // ( xxx ) (Token::RightParen, _) => Ok(expr), // ( @@ -314,7 +310,7 @@ fn parse_fn_call( #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; - let (token, token_pos) = input.peek().unwrap(); + let (token, token_pos) = input.peek().expect(NEVER_ENDS); let mut args = StaticVec::new(); @@ -333,14 +329,15 @@ fn parse_fn_call( Token::RightParen => { eat_token(input, Token::RightParen); - let hash = if let Some(ref mut modules) = namespace { - #[cfg(not(feature = "no_module"))] - modules.set_index(state.find_module(&modules[0].name)); + let hash = namespace.as_mut().map_or_else( + || calc_fn_hash(&id, 0), + |modules| { + #[cfg(not(feature = "no_module"))] + modules.set_index(state.find_module(&modules[0].name)); - calc_fn_hash(modules.iter().map(|m| m.name.as_str()), &id, 0) - } else { - calc_fn_hash(empty(), &id, 0) - }; + calc_qualified_fn_hash(modules.iter().map(|m| m.name.as_str()), &id, 0) + }, + ); let hashes = if is_valid_identifier(id.chars()) { FnCallHashes::from_script(hash) @@ -369,25 +366,30 @@ fn parse_fn_call( let settings = settings.level_up(); loop { - match input.peek().unwrap() { + match input.peek().expect(NEVER_ENDS) { // id(...args, ) - handle trailing comma (Token::RightParen, _) => (), _ => args.push(parse_expr(input, state, lib, settings)?), } - match input.peek().unwrap() { + match input.peek().expect(NEVER_ENDS) { // id(...args) (Token::RightParen, _) => { eat_token(input, Token::RightParen); - let hash = if let Some(ref mut modules) = namespace { - #[cfg(not(feature = "no_module"))] - modules.set_index(state.find_module(&modules[0].name)); + let hash = namespace.as_mut().map_or_else( + || calc_fn_hash(&id, args.len()), + |modules| { + #[cfg(not(feature = "no_module"))] + modules.set_index(state.find_module(&modules[0].name)); - calc_fn_hash(modules.iter().map(|m| m.name.as_str()), &id, args.len()) - } else { - calc_fn_hash(empty(), &id, args.len()) - }; + calc_qualified_fn_hash( + modules.iter().map(|m| m.name.as_str()), + &id, + args.len(), + ) + }, + ); let hashes = if is_valid_identifier(id.chars()) { FnCallHashes::from_script(hash) @@ -558,12 +560,12 @@ fn parse_index_chain( } // Check if there is a closing bracket - match input.peek().unwrap() { + match input.peek().expect(NEVER_ENDS) { (Token::RightBracket, _) => { eat_token(input, Token::RightBracket); // Any more indexing following? - match input.peek().unwrap() { + match input.peek().expect(NEVER_ENDS) { // If another indexing level, right-bind it (Token::LeftBracket, _) => { let prev_pos = settings.pos; @@ -618,10 +620,10 @@ fn parse_array_literal( "Size of array literal".to_string(), state.engine.max_array_size(), ) - .into_err(input.peek().unwrap().1)); + .into_err(input.peek().expect(NEVER_ENDS).1)); } - match input.peek().unwrap() { + match input.peek().expect(NEVER_ENDS) { (Token::RightBracket, _) => { eat_token(input, Token::RightBracket); break; @@ -638,7 +640,7 @@ fn parse_array_literal( } } - match input.peek().unwrap() { + match input.peek().expect(NEVER_ENDS) { (Token::Comma, _) => { eat_token(input, Token::Comma); } @@ -685,7 +687,7 @@ fn parse_map_literal( loop { const MISSING_RBRACE: &str = "to end this object map literal"; - match input.peek().unwrap() { + match input.peek().expect(NEVER_ENDS) { (Token::RightBrace, _) => { eat_token(input, Token::RightBrace); break; @@ -699,7 +701,7 @@ fn parse_map_literal( _ => (), } - let (name, pos) = match input.next().unwrap() { + let (name, pos) = match input.next().expect(NEVER_ENDS) { (Token::Identifier(s), pos) | (Token::StringConstant(s), pos) => { if map.iter().any(|(p, _)| p.name == s) { return Err(PERR::DuplicatedProperty(s).into_err(pos)); @@ -726,7 +728,7 @@ fn parse_map_literal( (_, pos) => return Err(PERR::PropertyExpected.into_err(pos)), }; - match input.next().unwrap() { + match input.next().expect(NEVER_ENDS) { (Token::Colon, _) => (), (Token::LexError(err), pos) => return Err(err.into_err(pos)), (_, pos) => { @@ -747,7 +749,7 @@ fn parse_map_literal( "Number of properties in object map literal".to_string(), state.engine.max_map_size(), ) - .into_err(input.peek().unwrap().1)); + .into_err(input.peek().expect(NEVER_ENDS).1)); } let expr = parse_expr(input, state, lib, settings.level_up())?; @@ -755,7 +757,7 @@ fn parse_map_literal( template.insert(name.clone().into(), Default::default()); map.push((Ident { name, pos }, expr)); - match input.peek().unwrap() { + match input.peek().expect(NEVER_ENDS) { (Token::Comma, _) => { eat_token(input, Token::Comma); } @@ -797,7 +799,7 @@ fn parse_switch( let item = parse_expr(input, state, lib, settings.level_up())?; - match input.next().unwrap() { + match input.next().expect(NEVER_ENDS) { (Token::LeftBrace, _) => (), (Token::LexError(err), pos) => return Err(err.into_err(pos)), (_, pos) => { @@ -816,7 +818,7 @@ fn parse_switch( loop { const MISSING_RBRACE: &str = "to end this switch block"; - let (expr, condition) = match input.peek().unwrap() { + let (expr, condition) = match input.peek().expect(NEVER_ENDS) { (Token::RightBrace, _) => { eat_token(input, Token::RightBrace); break; @@ -872,7 +874,7 @@ fn parse_switch( None }; - match input.next().unwrap() { + match input.next().expect(NEVER_ENDS) { (Token::DoubleArrow, _) => (), (Token::LexError(err), pos) => return Err(err.into_err(pos)), (_, pos) => { @@ -895,7 +897,7 @@ fn parse_switch( Some(stmt.into()) }; - match input.peek().unwrap() { + match input.peek().expect(NEVER_ENDS) { (Token::Comma, _) => { eat_token(input, Token::Comma); } @@ -938,7 +940,7 @@ fn parse_primary( #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; - let (token, token_pos) = input.peek().unwrap(); + let (token, token_pos) = input.peek().expect(NEVER_ENDS); settings.pos = *token_pos; let mut root_expr = match token { @@ -948,7 +950,7 @@ fn parse_primary( | Token::CharConstant(_) | Token::StringConstant(_) | Token::True - | Token::False => match input.next().unwrap().0 { + | Token::False => match input.next().expect(NEVER_ENDS).0 { Token::IntegerConstant(x) => Expr::IntegerConstant(x, settings.pos), Token::CharConstant(c) => Expr::CharConstant(c, settings.pos), Token::StringConstant(s) => { @@ -961,13 +963,13 @@ fn parse_primary( #[cfg(not(feature = "no_float"))] Token::FloatConstant(x) => { let x = (*x).into(); - input.next().unwrap(); + input.next().expect(NEVER_ENDS); Expr::FloatConstant(x, settings.pos) } #[cfg(feature = "decimal")] Token::DecimalConstant(x) => { let x = (*x).into(); - input.next().unwrap(); + input.next().expect(NEVER_ENDS); Expr::DynamicConstant(Box::new(x), settings.pos) } @@ -1019,7 +1021,7 @@ fn parse_primary( state.access_var(closure, *pos); }); - let hash_script = calc_fn_hash(empty(), &func.name, func.params.len()); + let hash_script = calc_fn_hash(&func.name, func.params.len()); lib.insert(hash_script, func.into()); expr @@ -1029,7 +1031,7 @@ fn parse_primary( Token::InterpolatedString(_) => { let mut segments: StaticVec = Default::default(); - if let (Token::InterpolatedString(s), pos) = input.next().unwrap() { + if let (Token::InterpolatedString(s), pos) = input.next().expect(NEVER_ENDS) { segments.push(Expr::StringConstant(s.into(), pos)); } else { unreachable!(); @@ -1047,7 +1049,7 @@ fn parse_primary( control.is_within_text = true; state.tokenizer_control.set(control); - match input.next().unwrap() { + match input.next().expect(NEVER_ENDS) { (Token::StringConstant(s), pos) => { if !s.is_empty() { segments.push(Expr::StringConstant(s.into(), pos)); @@ -1084,12 +1086,12 @@ fn parse_primary( // Identifier Token::Identifier(_) => { - let s = match input.next().unwrap() { + let s = match input.next().expect(NEVER_ENDS) { (Token::Identifier(s), _) => s, _ => unreachable!(), }; - match input.peek().unwrap().0 { + match input.peek().expect(NEVER_ENDS).0 { // Function call Token::LeftParen | Token::Bang => { #[cfg(not(feature = "no_closure"))] @@ -1138,12 +1140,12 @@ fn parse_primary( // Reserved keyword or symbol Token::Reserved(_) => { - let s = match input.next().unwrap() { + let s = match input.next().expect(NEVER_ENDS) { (Token::Reserved(s), _) => s, _ => unreachable!(), }; - match input.peek().unwrap().0 { + match input.peek().expect(NEVER_ENDS).0 { // Function call is allowed to have reserved keyword Token::LeftParen | Token::Bang if is_keyword_function(&s) => Expr::Variable( None, @@ -1168,7 +1170,7 @@ fn parse_primary( } } - Token::LexError(_) => match input.next().unwrap() { + Token::LexError(_) => match input.next().expect(NEVER_ENDS) { (Token::LexError(err), _) => return Err(err.into_err(settings.pos)), _ => unreachable!(), }, @@ -1180,13 +1182,13 @@ fn parse_primary( // Tail processing all possible postfix operators loop { - let (tail_token, _) = input.peek().unwrap(); + let (tail_token, _) = input.peek().expect(NEVER_ENDS); if !root_expr.is_valid_postfix(tail_token) { break; } - let (tail_token, tail_pos) = input.next().unwrap(); + let (tail_token, tail_pos) = input.next().expect(NEVER_ENDS); settings.pos = tail_pos; root_expr = match (root_expr, tail_token) { @@ -1226,33 +1228,35 @@ fn parse_primary( parse_fn_call(input, state, lib, name, false, ns, settings.level_up())? } // module access - (Expr::Variable(_, var_pos, x), Token::DoubleColon) => match input.next().unwrap() { - (Token::Identifier(id2), pos2) => { - let (_, mut namespace, var_name) = *x; - let var_name_def = Ident { - name: var_name, - pos: var_pos, - }; + (Expr::Variable(_, var_pos, x), Token::DoubleColon) => { + match input.next().expect(NEVER_ENDS) { + (Token::Identifier(id2), pos2) => { + let (_, mut namespace, var_name) = *x; + let var_name_def = Ident { + name: var_name, + pos: var_pos, + }; - if let Some((_, ref mut namespace)) = namespace { - namespace.push(var_name_def); - } else { - let mut ns: NamespaceRef = Default::default(); - ns.push(var_name_def); - namespace = Some((42, ns)); + if let Some((_, ref mut namespace)) = namespace { + namespace.push(var_name_def); + } else { + let mut ns: NamespaceRef = Default::default(); + ns.push(var_name_def); + namespace = Some((42, ns)); + } + + Expr::Variable( + None, + pos2, + Box::new((None, namespace, state.get_identifier(id2))), + ) } - - Expr::Variable( - None, - pos2, - Box::new((None, namespace, state.get_identifier(id2))), - ) + (Token::Reserved(id2), pos2) if is_valid_identifier(id2.chars()) => { + return Err(PERR::Reserved(id2).into_err(pos2)); + } + (_, pos2) => return Err(PERR::VariableExpected.into_err(pos2)), } - (Token::Reserved(id2), pos2) if is_valid_identifier(id2.chars()) => { - return Err(PERR::Reserved(id2).into_err(pos2)); - } - (_, pos2) => return Err(PERR::VariableExpected.into_err(pos2)), - }, + } // Indexing #[cfg(not(feature = "no_index"))] (expr, Token::LeftBracket) => { @@ -1262,7 +1266,7 @@ fn parse_primary( #[cfg(not(feature = "no_object"))] (expr, Token::Period) => { // Expression after dot must start with an identifier - match input.peek().unwrap() { + match input.peek().expect(NEVER_ENDS) { (Token::Identifier(_), _) => { #[cfg(not(feature = "no_closure"))] { @@ -1298,7 +1302,7 @@ fn parse_primary( } .map(|x| match x.as_mut() { (_, Some((hash, namespace)), name) => { - *hash = calc_fn_hash(namespace.iter().map(|v| v.name.as_str()), name, 0); + *hash = calc_qualified_fn_hash(namespace.iter().map(|v| v.name.as_str()), name, 0); #[cfg(not(feature = "no_module"))] namespace.set_index(state.find_module(&namespace[0].name)); @@ -1317,7 +1321,7 @@ fn parse_unary( lib: &mut FunctionsLib, mut settings: ParseSettings, ) -> Result { - let (token, token_pos) = input.peek().unwrap(); + let (token, token_pos) = input.peek().expect(NEVER_ENDS); settings.pos = *token_pos; #[cfg(not(feature = "unchecked"))] @@ -1354,7 +1358,7 @@ fn parse_unary( Ok(Expr::FnCall( Box::new(FnCallExpr { name: state.get_identifier("-"), - hashes: FnCallHashes::from_native(calc_fn_hash(empty(), "-", 1)), + hashes: FnCallHashes::from_native(calc_fn_hash("-", 1)), args, ..Default::default() }), @@ -1381,7 +1385,7 @@ fn parse_unary( Ok(Expr::FnCall( Box::new(FnCallExpr { name: state.get_identifier("+"), - hashes: FnCallHashes::from_native(calc_fn_hash(empty(), "+", 1)), + hashes: FnCallHashes::from_native(calc_fn_hash("+", 1)), args, ..Default::default() }), @@ -1400,7 +1404,7 @@ fn parse_unary( Ok(Expr::FnCall( Box::new(FnCallExpr { name: state.get_identifier("!"), - hashes: FnCallHashes::from_native(calc_fn_hash(empty(), "!", 1)), + hashes: FnCallHashes::from_native(calc_fn_hash("!", 1)), args, ..Default::default() }), @@ -1453,7 +1457,16 @@ fn make_assignment_stmt<'a>( // var (indexed) = rhs Expr::Variable(i, var_pos, x) => { let (index, _, name) = x.as_ref(); - let index = i.map_or_else(|| index.unwrap().get(), |n| n.get() as usize); + let index = i.map_or_else( + || { + index + .expect( + "never fails because the long index is `Some` when the short index is `None`", + ) + .get() + }, + |n| n.get() as usize, + ); match state.stack[state.stack.len() - index].1 { AccessMode::ReadWrite => { Ok(Stmt::Assignment(Box::new((lhs, op_info, rhs)), op_pos)) @@ -1502,12 +1515,15 @@ fn parse_op_assignment_stmt( #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; - let (token, token_pos) = input.peek().unwrap(); + let (token, token_pos) = input.peek().expect(NEVER_ENDS); settings.pos = *token_pos; let (op, pos) = match token { - Token::Equals => (None, input.next().unwrap().1), - _ if token.is_op_assignment() => input.next().map(|(op, pos)| (Some(op), pos)).unwrap(), + Token::Equals => (None, input.next().expect(NEVER_ENDS).1), + _ if token.is_op_assignment() => input + .next() + .map(|(op, pos)| (Some(op), pos)) + .expect(NEVER_ENDS), _ => return Ok(Stmt::Expr(lhs)), }; @@ -1534,24 +1550,22 @@ fn make_dot_expr( (lhs, Expr::Variable(_, var_pos, x)) if x.1.is_none() => { let ident = x.2; let getter = state.get_identifier(crate::engine::make_getter(&ident)); - let hash_get = calc_fn_hash(empty(), &getter, 1); + let hash_get = calc_fn_hash(&getter, 1); let setter = state.get_identifier(crate::engine::make_setter(&ident)); - let hash_set = calc_fn_hash(empty(), &setter, 2); + let hash_set = calc_fn_hash(&setter, 2); let rhs = Expr::Property(Box::new(( (getter, hash_get), (setter, hash_set), - Ident { - name: state.get_identifier(ident), - pos: var_pos, - }, + (state.get_identifier(ident).into(), var_pos), ))); Expr::Dot(Box::new(BinaryExpr { lhs, rhs }), op_pos) } // lhs.module::id - syntax error (_, Expr::Variable(_, _, x)) if x.1.is_some() => { - return Err(PERR::PropertyExpected.into_err(x.1.unwrap().1[0].pos)) + return Err(PERR::PropertyExpected + .into_err(x.1.expect("never fails because the namespace is `Some`").1[0].pos)) } // lhs.prop (lhs, prop @ Expr::Property(_)) => { @@ -1572,8 +1586,8 @@ fn make_dot_expr( Expr::FnCall(mut func, func_pos) => { // Recalculate hash func.hashes = FnCallHashes::from_script_and_native( - calc_fn_hash(empty(), &func.name, func.args_count()), - calc_fn_hash(empty(), &func.name, func.args_count() + 1), + calc_fn_hash(&func.name, func.args_count()), + calc_fn_hash(&func.name, func.args_count() + 1), ); let rhs = Expr::Dot( @@ -1628,8 +1642,8 @@ fn make_dot_expr( (lhs, Expr::FnCall(mut func, func_pos)) => { // Recalculate hash func.hashes = FnCallHashes::from_script_and_native( - calc_fn_hash(empty(), &func.name, func.args_count()), - calc_fn_hash(empty(), &func.name, func.args_count() + 1), + calc_fn_hash(&func.name, func.args_count()), + calc_fn_hash(&func.name, func.args_count() + 1), ); let rhs = Expr::FnCall(func, func_pos); Expr::Dot(Box::new(BinaryExpr { lhs, rhs }), op_pos) @@ -1656,7 +1670,7 @@ fn parse_binary_op( let mut root = lhs; loop { - let (current_op, current_pos) = input.peek().unwrap(); + let (current_op, current_pos) = input.peek().expect(NEVER_ENDS); let precedence = match current_op { Token::Custom(c) => state .engine @@ -1677,11 +1691,11 @@ fn parse_binary_op( return Ok(root); } - let (op_token, pos) = input.next().unwrap(); + let (op_token, pos) = input.next().expect(NEVER_ENDS); let rhs = parse_unary(input, state, lib, settings)?; - let (next_op, next_pos) = input.peek().unwrap(); + let (next_op, next_pos) = input.peek().expect(NEVER_ENDS); let next_precedence = match next_op { Token::Custom(c) => state .engine @@ -1711,7 +1725,7 @@ fn parse_binary_op( settings.ensure_level_within_max_limit(state.max_expr_depth)?; let op = op_token.syntax(); - let hash = calc_fn_hash(empty(), &op, 2); + let hash = calc_fn_hash(&op, 2); let op_base = FnCallExpr { name: state.get_identifier(op.as_ref()), @@ -1751,8 +1765,12 @@ fn parse_binary_op( } Token::Or => { - let rhs = args.pop().unwrap(); - let current_lhs = args.pop().unwrap(); + let rhs = args + .pop() + .expect("never fails because `||` has two arguments"); + let current_lhs = args + .pop() + .expect("never fails because `||` has two arguments"); Expr::Or( Box::new(BinaryExpr { lhs: current_lhs, @@ -1762,8 +1780,12 @@ fn parse_binary_op( ) } Token::And => { - let rhs = args.pop().unwrap(); - let current_lhs = args.pop().unwrap(); + let rhs = args + .pop() + .expect("never fails because `&&` has two arguments"); + let current_lhs = args + .pop() + .expect("never fails because `&&` has two arguments"); Expr::And( Box::new(BinaryExpr { lhs: current_lhs, @@ -1779,7 +1801,7 @@ fn parse_binary_op( args.shrink_to_fit(); // Convert into a call to `contains` - let hash = calc_fn_hash(empty(), OP_CONTAINS, 2); + let hash = calc_fn_hash(OP_CONTAINS, 2); Expr::FnCall( Box::new(FnCallExpr { hashes: FnCallHashes::from_script(hash), @@ -1798,7 +1820,7 @@ fn parse_binary_op( .get(s.as_str()) .map_or(false, Option::is_some) => { - let hash = calc_fn_hash(empty(), &s, 2); + let hash = calc_fn_hash(&s, 2); Expr::FnCall( Box::new(FnCallExpr { @@ -1848,7 +1870,7 @@ fn parse_custom_syntax( tokens.push(key.into()); loop { - let (fwd_token, fwd_pos) = input.peek().unwrap(); + let (fwd_token, fwd_pos) = input.peek().expect(NEVER_ENDS); settings.pos = *fwd_pos; let settings = settings.level_up(); @@ -1861,7 +1883,7 @@ fn parse_custom_syntax( }; match required_token.as_str() { - MARKER_IDENT => match input.next().unwrap() { + MARKER_IDENT => match input.next().expect(NEVER_ENDS) { (Token::Identifier(s), pos) => { let name = state.get_identifier(s); segments.push(name.clone().into()); @@ -1888,7 +1910,7 @@ fn parse_custom_syntax( } stmt => unreachable!("expecting Stmt::Block, but gets {:?}", stmt), }, - s => match input.next().unwrap() { + s => match input.next().expect(NEVER_ENDS) { (Token::LexError(err), pos) => return Err(err.into_err(pos)), (t, _) if t.syntax().as_ref() == s => { segments.push(required_token.clone()); @@ -1928,18 +1950,18 @@ fn parse_expr( #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; - settings.pos = input.peek().unwrap().1; + settings.pos = input.peek().expect(NEVER_ENDS).1; // Check if it is a custom syntax. if !state.engine.custom_syntax.is_empty() { - let (token, pos) = input.peek().unwrap(); + let (token, pos) = input.peek().expect(NEVER_ENDS); let token_pos = *pos; match token { Token::Custom(key) | Token::Reserved(key) | Token::Identifier(key) => { match state.engine.custom_syntax.get_key_value(key.as_str()) { Some((key, syntax)) => { - input.next().unwrap(); + input.next().expect(NEVER_ENDS); return parse_custom_syntax( input, state, lib, settings, key, syntax, token_pos, ); @@ -1965,7 +1987,7 @@ fn parse_expr( /// Make sure that the expression is not a statement expression (i.e. wrapped in `{}`). fn ensure_not_statement_expr(input: &mut TokenStream, type_name: &str) -> Result<(), ParseError> { - match input.peek().unwrap() { + match input.peek().expect(NEVER_ENDS) { // Disallow statement expressions (Token::LeftBrace, pos) | (Token::EOF, pos) => { Err(PERR::ExprExpected(type_name.to_string()).into_err(*pos)) @@ -1977,7 +1999,7 @@ fn ensure_not_statement_expr(input: &mut TokenStream, type_name: &str) -> Result /// Make sure that the expression is not a mis-typed assignment (i.e. `a = b` instead of `a == b`). fn ensure_not_assignment(input: &mut TokenStream) -> Result<(), ParseError> { - match input.peek().unwrap() { + match input.peek().expect(NEVER_ENDS) { (Token::Equals, pos) => Err(LexError::ImproperSymbol( "=".to_string(), "Possibly a typo of '=='?".to_string(), @@ -2024,7 +2046,7 @@ fn parse_if( // if guard { if_body } else ... let else_body = if match_token(input, Token::Else).0 { - if let (Token::If, _) = input.peek().unwrap() { + if let (Token::If, _) = input.peek().expect(NEVER_ENDS) { // if guard { if_body } else if ... parse_if(input, state, lib, settings.level_up())? } else { @@ -2053,7 +2075,7 @@ fn parse_while_loop( settings.ensure_level_within_max_limit(state.max_expr_depth)?; // while|loops ... - let (guard, token_pos) = match input.next().unwrap() { + let (guard, token_pos) = match input.next().expect(NEVER_ENDS) { (Token::While, pos) => { ensure_not_statement_expr(input, "a boolean")?; let expr = parse_expr(input, state, lib, settings.level_up())?; @@ -2088,7 +2110,7 @@ fn parse_do( settings.is_breakable = true; let body = parse_block(input, state, lib, settings.level_up())?; - let is_while = match input.next().unwrap() { + let is_while = match input.next().expect(NEVER_ENDS) { (Token::While, _) => true, (Token::Until, _) => false, (_, pos) => { @@ -2126,7 +2148,7 @@ fn parse_for( settings.pos = eat_token(input, Token::For); // for name ... - let (name, name_pos) = match input.next().unwrap() { + let (name, name_pos) = match input.next().expect(NEVER_ENDS) { // Variable name (Token::Identifier(s), pos) => (s, pos), // Reserved keyword @@ -2140,7 +2162,7 @@ fn parse_for( }; // for name in ... - match input.next().unwrap() { + match input.next().expect(NEVER_ENDS) { (Token::In, _) => (), (Token::LexError(err), pos) => return Err(err.into_err(pos)), (_, pos) => { @@ -2190,10 +2212,10 @@ fn parse_let( settings.ensure_level_within_max_limit(state.max_expr_depth)?; // let/const... (specified in `var_type`) - settings.pos = input.next().unwrap().1; + settings.pos = input.next().expect(NEVER_ENDS).1; // let name ... - let (name, pos) = match input.next().unwrap() { + let (name, pos) = match input.next().expect(NEVER_ENDS) { (Token::Identifier(s), pos) => (s, pos), (Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => { return Err(PERR::Reserved(s).into_err(pos)); @@ -2249,7 +2271,7 @@ fn parse_import( } // import expr as name ... - let (name, name_pos) = match input.next().unwrap() { + let (name, name_pos) = match input.next().expect(NEVER_ENDS) { (Token::Identifier(s), pos) => (s, pos), (Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => { return Err(PERR::Reserved(s).into_err(pos)); @@ -2287,7 +2309,7 @@ fn parse_export( settings.pos = eat_token(input, Token::Export); - match input.peek().unwrap() { + match input.peek().expect(NEVER_ENDS) { (Token::Let, pos) => { let pos = *pos; let mut stmt = parse_let(input, state, lib, AccessMode::ReadWrite, true, settings)?; @@ -2306,7 +2328,7 @@ fn parse_export( let mut exports = Vec::with_capacity(4); loop { - let (id, id_pos) = match input.next().unwrap() { + let (id, id_pos) = match input.next().expect(NEVER_ENDS) { (Token::Identifier(s), pos) => (s.clone(), pos), (Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => { return Err(PERR::Reserved(s).into_err(pos)); @@ -2316,7 +2338,7 @@ fn parse_export( }; let rename = if match_token(input, Token::As).0 { - match input.next().unwrap() { + match input.next().expect(NEVER_ENDS) { (Token::Identifier(s), pos) => Some(Ident { name: state.get_identifier(s), pos, @@ -2339,7 +2361,7 @@ fn parse_export( rename, )); - match input.peek().unwrap() { + match input.peek().expect(NEVER_ENDS) { (Token::Comma, _) => { eat_token(input, Token::Comma); } @@ -2365,7 +2387,7 @@ fn parse_block( mut settings: ParseSettings, ) -> Result { // Must start with { - settings.pos = match input.next().unwrap() { + settings.pos = match input.next().expect(NEVER_ENDS) { (Token::LeftBrace, pos) => pos, (Token::LexError(err), pos) => return Err(err.into_err(pos)), (_, pos) => { @@ -2390,7 +2412,7 @@ fn parse_block( loop { // Terminated? - match input.peek().unwrap() { + match input.peek().expect(NEVER_ENDS) { (Token::RightBrace, _) => { eat_token(input, Token::RightBrace); break; @@ -2419,7 +2441,7 @@ fn parse_block( statements.push(stmt); - match input.peek().unwrap() { + match input.peek().expect(NEVER_ENDS) { // { ... stmt } (Token::RightBrace, _) => { eat_token(input, Token::RightBrace); @@ -2468,7 +2490,7 @@ fn parse_expr_stmt( #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; - settings.pos = input.peek().unwrap().1; + settings.pos = input.peek().expect(NEVER_ENDS).1; let expr = parse_expr(input, state, lib, settings.level_up())?; let stmt = parse_op_assignment_stmt(input, state, lib, expr, settings.level_up())?; @@ -2491,7 +2513,7 @@ fn parse_stmt( let mut comments_pos = Position::NONE; // Handle doc-comments. - while let (Token::Comment(ref comment), pos) = input.peek().unwrap() { + while let (Token::Comment(ref comment), pos) = input.peek().expect(NEVER_ENDS) { if comments_pos.is_none() { comments_pos = *pos; } @@ -2504,11 +2526,11 @@ fn parse_stmt( return Err(PERR::WrongDocComment.into_err(comments_pos)); } - match input.next().unwrap().0 { + match input.next().expect(NEVER_ENDS).0 { Token::Comment(comment) => { comments.push(comment); - match input.peek().unwrap() { + match input.peek().expect(NEVER_ENDS) { (Token::Fn, _) | (Token::Private, _) => break, (Token::Comment(_), _) => (), _ => return Err(PERR::WrongDocComment.into_err(comments_pos)), @@ -2521,7 +2543,7 @@ fn parse_stmt( comments }; - let (token, token_pos) = match input.peek().unwrap() { + let (token, token_pos) = match input.peek().expect(NEVER_ENDS) { (Token::EOF, pos) => return Ok(Stmt::Noop(*pos)), x => x, }; @@ -2553,7 +2575,7 @@ fn parse_stmt( FnAccess::Public }; - match input.next().unwrap() { + match input.next().expect(NEVER_ENDS) { (Token::Fn, pos) => { let mut new_state = ParseState::new(state.engine, state.tokenizer_control.clone()); @@ -2586,7 +2608,7 @@ fn parse_stmt( comments, )?; - let hash = calc_fn_hash(empty(), &func.name, func.params.len()); + let hash = calc_fn_hash(&func.name, func.params.len()); if lib.contains_key(&hash) { return Err(PERR::FnDuplicatedDefinition( @@ -2638,9 +2660,9 @@ fn parse_stmt( pos, ) }) - .unwrap(); + .expect(NEVER_ENDS); - match input.peek().unwrap() { + match input.peek().expect(NEVER_ENDS) { // `return`/`throw` at (Token::EOF, _) => Ok(Stmt::Return(return_type, None, token_pos)), // `return;` or `throw;` @@ -2699,7 +2721,7 @@ fn parse_try_catch( // try { body } catch ( let var_def = if match_token(input, Token::LeftParen).0 { - let id = match input.next().unwrap() { + let id = match input.next().expect(NEVER_ENDS) { (Token::Identifier(s), pos) => Ident { name: state.get_identifier(s), pos, @@ -2747,7 +2769,7 @@ fn parse_fn( #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; - let (token, pos) = input.next().unwrap(); + let (token, pos) = input.next().expect(NEVER_ENDS); let name = token .into_function_name_for_override() @@ -2756,7 +2778,7 @@ fn parse_fn( _ => PERR::FnMissingName.into_err(pos), })?; - match input.peek().unwrap() { + match input.peek().expect(NEVER_ENDS) { (Token::LeftParen, _) => eat_token(input, Token::LeftParen), (_, pos) => return Err(PERR::FnMissingParams(name).into_err(*pos)), }; @@ -2767,7 +2789,7 @@ fn parse_fn( let sep_err = format!("to separate the parameters of function '{}'", name); loop { - match input.next().unwrap() { + match input.next().expect(NEVER_ENDS) { (Token::RightParen, _) => break, (Token::Identifier(s), pos) => { if params.iter().any(|(p, _)| p == &s) { @@ -2787,7 +2809,7 @@ fn parse_fn( } } - match input.next().unwrap() { + match input.next().expect(NEVER_ENDS) { (Token::RightParen, _) => break, (Token::Comma, _) => (), (Token::LexError(err), pos) => return Err(err.into_err(pos)), @@ -2799,7 +2821,7 @@ fn parse_fn( } // Parse function body - let body = match input.peek().unwrap() { + let body = match input.peek().expect(NEVER_ENDS) { (Token::LeftBrace, _) => { settings.is_breakable = false; parse_block(input, state, lib, settings.level_up())? @@ -2869,7 +2891,6 @@ fn make_curry_from_externals( Box::new(FnCallExpr { name: state.get_identifier(crate::engine::KEYWORD_FN_PTR_CURRY), hashes: FnCallHashes::from_native(calc_fn_hash( - empty(), crate::engine::KEYWORD_FN_PTR_CURRY, num_externals + 1, )), @@ -2900,10 +2921,10 @@ fn parse_anon_fn( let mut params: StaticVec<_> = Default::default(); - if input.next().unwrap().0 != Token::Or { + if input.next().expect(NEVER_ENDS).0 != Token::Or { if !match_token(input, Token::Pipe).0 { loop { - match input.next().unwrap() { + match input.next().expect(NEVER_ENDS) { (Token::Pipe, _) => break, (Token::Identifier(s), pos) => { if params.iter().any(|(p, _)| p == &s) { @@ -2923,7 +2944,7 @@ fn parse_anon_fn( } } - match input.next().unwrap() { + match input.next().expect(NEVER_ENDS) { (Token::Pipe, _) => break, (Token::Comma, _) => (), (Token::LexError(err), pos) => return Err(err.into_err(pos)), @@ -3027,7 +3048,7 @@ impl Engine { assert!(functions.is_empty()); - match input.peek().unwrap() { + match input.peek().expect(NEVER_ENDS) { (Token::EOF, _) => (), // Return error if the expression doesn't end (token, pos) => { @@ -3052,7 +3073,7 @@ impl Engine { let mut statements = Vec::with_capacity(16); let mut functions = BTreeMap::new(); - while !input.peek().unwrap().0.is_eof() { + while !input.peek().expect(NEVER_ENDS).0.is_eof() { let settings = ParseSettings { allow_if_expr: true, allow_switch_expr: true, @@ -3075,7 +3096,7 @@ impl Engine { statements.push(stmt); - match input.peek().unwrap() { + match input.peek().expect(NEVER_ENDS) { // EOF (Token::EOF, _) => break, // stmt ; @@ -3121,29 +3142,32 @@ impl Engine { } } -/// Map a `Dynamic` value to an expression. -/// -/// Returns Some(expression) if conversion is successful. Otherwise None. -pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> Option { - match value.0 { - #[cfg(not(feature = "no_float"))] - Union::Float(value, _, _) => Some(Expr::FloatConstant(value, pos)), +impl From for Expr { + fn from(value: Dynamic) -> Self { + match value.0 { + #[cfg(not(feature = "no_float"))] + Union::Float(value, _, _) => Self::FloatConstant(value, Position::NONE), - #[cfg(feature = "decimal")] - Union::Decimal(value, _, _) => Some(Expr::DynamicConstant(Box::new((*value).into()), pos)), + #[cfg(feature = "decimal")] + Union::Decimal(value, _, _) => { + Self::DynamicConstant(Box::new((*value).into()), Position::NONE) + } - Union::Unit(_, _, _) => Some(Expr::Unit(pos)), - Union::Int(value, _, _) => Some(Expr::IntegerConstant(value, pos)), - Union::Char(value, _, _) => Some(Expr::CharConstant(value, pos)), - Union::Str(value, _, _) => Some(Expr::StringConstant(value, pos)), - Union::Bool(value, _, _) => Some(Expr::BoolConstant(value, pos)), + Union::Unit(_, _, _) => Self::Unit(Position::NONE), + Union::Int(value, _, _) => Self::IntegerConstant(value, Position::NONE), + Union::Char(value, _, _) => Self::CharConstant(value, Position::NONE), + Union::Str(value, _, _) => Self::StringConstant(value, Position::NONE), + Union::Bool(value, _, _) => Self::BoolConstant(value, Position::NONE), - #[cfg(not(feature = "no_index"))] - Union::Array(array, _, _) => Some(Expr::DynamicConstant(Box::new((*array).into()), pos)), + #[cfg(not(feature = "no_index"))] + Union::Array(array, _, _) => { + Self::DynamicConstant(Box::new((*array).into()), Position::NONE) + } - #[cfg(not(feature = "no_object"))] - Union::Map(map, _, _) => Some(Expr::DynamicConstant(Box::new((*map).into()), pos)), + #[cfg(not(feature = "no_object"))] + Union::Map(map, _, _) => Self::DynamicConstant(Box::new((*map).into()), Position::NONE), - _ => None, + _ => Self::DynamicConstant(Box::new(value.into()), Position::NONE), + } } } diff --git a/src/result.rs b/src/result.rs index 8a873f2a..172e9834 100644 --- a/src/result.rs +++ b/src/result.rs @@ -181,7 +181,9 @@ impl fmt::Display for EvalAltResult { | Self::ErrorTerminated(_, _) => f.write_str(desc)?, Self::ErrorRuntime(d, _) if d.is::() => { - let s = &*d.read_lock::().unwrap(); + let s = &*d + .read_lock::() + .expect("never fails because the type was checked"); write!(f, "{}: {}", desc, if s.is_empty() { desc } else { s })? } Self::ErrorRuntime(d, _) if d.is::<()>() => f.write_str(desc)?, @@ -336,7 +338,11 @@ impl EvalAltResult { pub(crate) fn dump_fields(&self, map: &mut crate::Map) { map.insert( "error".into(), - format!("{:?}", self).split('(').next().unwrap().into(), + format!("{:?}", self) + .split('(') + .next() + .expect("never fails because the debug format of an error is `ErrorXXX(...)`") + .into(), ); match self { diff --git a/src/scope.rs b/src/scope.rs index 373ad0cb..1ba7a2f3 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -7,7 +7,7 @@ use std::prelude::v1::*; use std::{borrow::Cow, iter::Extend}; /// Keep a number of entries inline (since [`Dynamic`] is usually small enough). -const SCOPE_SIZE: usize = 16; +const SCOPE_SIZE: usize = 8; /// Type containing information about the current scope. /// Useful for keeping state between [`Engine`][crate::Engine] evaluation runs. @@ -62,10 +62,7 @@ pub struct Scope<'a> { impl Default for Scope<'_> { #[inline(always)] fn default() -> Self { - Self { - values: Default::default(), - names: Vec::with_capacity(SCOPE_SIZE), - } + Self::new() } } @@ -99,7 +96,10 @@ impl<'a> Scope<'a> { /// ``` #[inline(always)] pub fn new() -> Self { - Default::default() + Self { + values: Default::default(), + names: Default::default(), + } } /// Empty the [`Scope`]. /// @@ -373,7 +373,11 @@ impl<'a> Scope<'a> { } Some((_, AccessMode::ReadOnly)) => panic!("variable {} is constant", name), Some((index, AccessMode::ReadWrite)) => { - *self.values.get_mut(index).unwrap() = Dynamic::from(value); + let value_ref = self + .values + .get_mut(index) + .expect("never fails because the index is returned by `get_index`"); + *value_ref = Dynamic::from(value); } } self @@ -406,24 +410,38 @@ impl<'a> Scope<'a> { }) } /// Get a mutable reference to an entry in the [`Scope`] based on the index. + /// + /// # Panics + /// + /// Panics if the index is out of bounds. #[inline(always)] pub(crate) fn get_mut_by_index(&mut self, index: usize) -> &mut Dynamic { - self.values.get_mut(index).expect("invalid index in Scope") + self.values + .get_mut(index) + .expect("never fails unless the index is out of bounds") } /// Update the access type of an entry in the [`Scope`]. + /// + /// # Panics + /// + /// Panics if the index is out of bounds. #[cfg(not(feature = "no_module"))] #[inline(always)] - pub(crate) fn add_entry_alias( - &mut self, - index: usize, - alias: impl Into + PartialEq, - ) -> &mut Self { - let entry = self.names.get_mut(index).expect("invalid index in Scope"); + pub(crate) fn add_entry_alias(&mut self, index: usize, alias: Identifier) -> &mut Self { + let entry = self + .names + .get_mut(index) + .expect("never fails unless the index is out of bounds"); if entry.1.is_none() { + // Initialize the alias list if it is empty. entry.1 = Some(Default::default()); } - if !entry.1.as_ref().unwrap().iter().any(|a| &alias == a) { - entry.1.as_mut().unwrap().push(alias.into()); + let list = entry + .1 + .as_mut() + .expect("never fails because the list is initialized"); + if !list.iter().any(|a| &alias == a) { + list.push(alias); } self } diff --git a/src/serde/metadata.rs b/src/serde/metadata.rs index 2fe005fd..79fe947e 100644 --- a/src/serde/metadata.rs +++ b/src/serde/metadata.rs @@ -91,21 +91,19 @@ struct FnMetadata { impl PartialOrd for FnMetadata { fn partial_cmp(&self, other: &Self) -> Option { - Some(match self.name.partial_cmp(&other.name).unwrap() { - Ordering::Less => Ordering::Less, - Ordering::Greater => Ordering::Greater, - Ordering::Equal => match self.num_params.partial_cmp(&other.num_params).unwrap() { - Ordering::Less => Ordering::Less, - Ordering::Greater => Ordering::Greater, - Ordering::Equal => self.params.partial_cmp(&other.params).unwrap(), - }, - }) + Some(self.cmp(other)) } } impl Ord for FnMetadata { fn cmp(&self, other: &Self) -> Ordering { - self.partial_cmp(other).unwrap() + match self.name.cmp(&other.name) { + Ordering::Equal => match self.num_params.cmp(&other.num_params) { + Ordering::Equal => self.params.cmp(&other.params), + cmp => cmp, + }, + cmp => cmp, + } } } diff --git a/src/token.rs b/src/token.rs index 44826444..992f0601 100644 --- a/src/token.rs +++ b/src/token.rs @@ -1181,7 +1181,10 @@ pub fn parse_string_literal( #[cfg(not(feature = "no_position"))] { - skip_whitespace_until = start.position().unwrap() + 1; + let start_position = start + .position() + .expect("never fails because the string must have a starting position"); + skip_whitespace_until = start_position + 1; } } @@ -1201,7 +1204,11 @@ pub fn parse_string_literal( // Whitespace to skip #[cfg(not(feature = "no_position"))] - _ if next_char.is_whitespace() && pos.position().unwrap() < skip_whitespace_until => {} + _ if next_char.is_whitespace() + && pos + .position() + .expect("never fails because a character must have a position") + < skip_whitespace_until => {} // All other characters _ => { @@ -1237,37 +1244,29 @@ fn scan_block_comment( stream: &mut impl InputStream, mut level: usize, pos: &mut Position, - comment: &mut Option, + mut comment: Option<&mut String>, ) -> usize { + let comment = &mut comment; + while let Some(c) = stream.get_next() { pos.advance(); - if let Some(ref mut comment) = comment { - comment.push(c); - } + comment.as_mut().map(|comment| comment.push(c)); match c { '/' => { - if let Some(c2) = stream.peek_next() { - if c2 == '*' { - eat_next(stream, pos); - if let Some(ref mut comment) = comment { - comment.push(c2); - } - level += 1; - } - } + stream.peek_next().filter(|&c2| c2 == '*').map(|c2| { + eat_next(stream, pos); + comment.as_mut().map(|comment| comment.push(c2)); + level += 1; + }); } '*' => { - if let Some(c2) = stream.peek_next() { - if c2 == '/' { - eat_next(stream, pos); - if let Some(ref mut comment) = comment { - comment.push(c2); - } - level -= 1; - } - } + stream.peek_next().filter(|&c2| c2 == '/').map(|c2| { + eat_next(stream, pos); + comment.as_mut().map(|comment| comment.push(c2)); + level -= 1; + }); } '\n' => pos.new_line(), _ => (), @@ -1347,21 +1346,27 @@ fn get_next_token_inner( None }; - state.comment_level = scan_block_comment(stream, state.comment_level, pos, &mut comment); + state.comment_level = + scan_block_comment(stream, state.comment_level, pos, comment.as_mut()); - let include_comments = state.include_comments; + let return_comment = state.include_comments; #[cfg(not(feature = "no_function"))] #[cfg(feature = "metadata")] - let include_comments = if is_doc_comment(comment.as_ref().unwrap()) { - true - } else { - include_comments - }; + let return_comment = return_comment + || is_doc_comment( + comment + .as_ref() + .expect("never fails because `include_comments` is true"), + ); - if include_comments { - return Some((Token::Comment(comment.unwrap()), start_pos)); - } else if state.comment_level > 0 { + if return_comment { + return Some(( + Token::Comment(comment.expect("never fails because `return_comment` is true")), + start_pos, + )); + } + if state.comment_level > 0 { // Reached EOF without ending comment block return None; } @@ -1409,7 +1414,7 @@ fn get_next_token_inner( } #[cfg(any(not(feature = "no_float"), feature = "decimal"))] '.' => { - stream.get_next().unwrap(); + stream.get_next().expect("never fails because it is `.`"); // Check if followed by digits or something that cannot start a property name match stream.peek_next().unwrap_or('\0') { @@ -1443,7 +1448,7 @@ fn get_next_token_inner( } #[cfg(not(feature = "no_float"))] 'e' => { - stream.get_next().unwrap(); + stream.get_next().expect("never fails it is `e`"); // Check if followed by digits or +/- match stream.peek_next().unwrap_or('\0') { @@ -1456,7 +1461,11 @@ fn get_next_token_inner( '+' | '-' => { result.push(next_char); pos.advance(); - result.push(stream.get_next().unwrap()); + result.push( + stream + .get_next() + .expect("never fails because it is `+` or `-`"), + ); pos.advance(); } // Not a floating-point number @@ -1492,12 +1501,10 @@ fn get_next_token_inner( } } - let num_pos = if let Some(negated_pos) = negated { + let num_pos = negated.map_or(start_pos, |negated_pos| { result.insert(0, '-'); negated_pos - } else { - start_pos - }; + }); // Parse number return Some(( @@ -1718,9 +1725,7 @@ fn get_next_token_inner( pos.new_line(); break; } - if let Some(ref mut comment) = comment { - comment.push(c); - } + comment.as_mut().map(|comment| comment.push(c)); pos.advance(); } @@ -1749,7 +1754,7 @@ fn get_next_token_inner( }; state.comment_level = - scan_block_comment(stream, state.comment_level, pos, &mut comment); + scan_block_comment(stream, state.comment_level, pos, comment.as_mut()); if let Some(comment) = comment { return Some((Token::Comment(comment), start_pos)); @@ -2179,8 +2184,8 @@ impl<'a> Iterator for TokenIterator<'a> { }; // Run the mapper, if any - let token = if let Some(map) = self.map { - map(token) + let token = if let Some(map_func) = self.map { + map_func(token) } else { token }; diff --git a/src/utils.rs b/src/utils.rs index ab998c5d..49012ea3 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -10,7 +10,7 @@ use std::{ cmp::Ordering, fmt, hash::{BuildHasher, Hash, Hasher}, - iter::FromIterator, + iter::{empty, FromIterator}, ops::{Add, AddAssign, Deref, Sub, SubAssign}, str::FromStr, }; @@ -58,9 +58,8 @@ pub fn get_hasher() -> ahash::AHasher { Default::default() } -/// _(INTERNALS)_ Calculate a [`u64`] hash key from a namespace-qualified function name +/// Calculate a [`u64`] hash key from a namespace-qualified function name /// and the number of parameters, but no parameter types. -/// Exported under the `internals` feature only. /// /// Module names are passed in via `&str` references from an iterator. /// Parameter types are passed in via [`TypeId`] values from an iterator. @@ -68,8 +67,8 @@ pub fn get_hasher() -> ahash::AHasher { /// # Note /// /// The first module name is skipped. Hashing starts from the _second_ module in the chain. -#[inline(always)] -pub fn calc_fn_hash<'a>( +#[inline] +pub fn calc_qualified_fn_hash<'a>( modules: impl Iterator, fn_name: impl AsRef, num: usize, @@ -88,11 +87,19 @@ pub fn calc_fn_hash<'a>( s.finish() } -/// _(INTERNALS)_ Calculate a [`u64`] hash key from a list of parameter types. -/// Exported under the `internals` feature only. +/// Calculate a [`u64`] hash key from a non-namespace-qualified function name +/// and the number of parameters, but no parameter types. /// /// Parameter types are passed in via [`TypeId`] values from an iterator. #[inline(always)] +pub fn calc_fn_hash(fn_name: impl AsRef, num: usize) -> u64 { + calc_qualified_fn_hash(empty(), fn_name, num) +} + +/// Calculate a [`u64`] hash key from a list of parameter types. +/// +/// Parameter types are passed in via [`TypeId`] values from an iterator. +#[inline] pub fn calc_fn_params_hash(params: impl Iterator) -> u64 { let s = &mut get_hasher(); let mut len = 0; diff --git a/tests/get_set.rs b/tests/get_set.rs index a55f5854..ab0894a9 100644 --- a/tests/get_set.rs +++ b/tests/get_set.rs @@ -1,6 +1,6 @@ #![cfg(not(feature = "no_object"))] -use rhai::{Engine, EvalAltResult, ImmutableString, INT}; +use rhai::{Engine, EvalAltResult, INT}; #[test] fn test_get_set() -> Result<(), Box> { @@ -46,21 +46,26 @@ fn test_get_set() -> Result<(), Box> { assert_eq!(engine.eval::("let a = new_ts(); a.x.add(); a.x")?, 42); assert_eq!(engine.eval::("let a = new_ts(); a.y.add(); a.y")?, 0); - #[cfg(not(feature = "no_index"))] - { - engine.register_indexer_get_set( - |value: &mut TestStruct, index: ImmutableString| value.array[index.len()], - |value: &mut TestStruct, index: ImmutableString, new_val: INT| { - value.array[index.len()] = new_val - }, - ); + engine.register_indexer_get_set( + |value: &mut TestStruct, index: &str| value.array[index.len()], + |value: &mut TestStruct, index: &str, new_val: INT| value.array[index.len()] = new_val, + ); + + #[cfg(not(feature = "no_index"))] + assert_eq!(engine.eval::(r#"let a = new_ts(); a["abc"]"#)?, 4); + + #[cfg(not(feature = "no_index"))] + assert_eq!( + engine.eval::(r#"let a = new_ts(); a["abc"] = 42; a["abc"]"#)?, + 42 + ); + + assert_eq!(engine.eval::(r"let a = new_ts(); a.abc")?, 4); + assert_eq!( + engine.eval::(r"let a = new_ts(); a.abc = 42; a.abc")?, + 42 + ); - assert_eq!(engine.eval::(r#"let a = new_ts(); a["abc"]"#)?, 4); - assert_eq!( - engine.eval::(r#"let a = new_ts(); a["abc"] = 42; a["abc"]"#)?, - 42 - ); - } Ok(()) } diff --git a/tests/optimizer.rs b/tests/optimizer.rs index 91b29a5c..844577b5 100644 --- a/tests/optimizer.rs +++ b/tests/optimizer.rs @@ -59,21 +59,21 @@ fn test_optimizer_parse() -> Result<(), Box> { assert_eq!( format!("{:?}", ast), - "AST { source: None, body: [Expr(123 @ 1:53)], functions: Module, resolver: None }" + "AST { source: None, body: Block[Expr(123 @ 1:53)], functions: Module, resolver: None }" ); let ast = engine.compile("const DECISION = false; if DECISION { 42 } else { 123 }")?; assert_eq!( format!("{:?}", ast), - r#"AST { source: None, body: [Const(false @ 1:18, "DECISION" @ 1:7, false, 1:1), Expr(123 @ 1:51)], functions: Module, resolver: None }"# + r#"AST { source: None, body: Block[Const(false @ 1:18, "DECISION" @ 1:7, false, 1:1), Expr(123 @ 1:51)], functions: Module, resolver: None }"# ); let ast = engine.compile("if 1 == 2 { 42 }")?; assert_eq!( format!("{:?}", ast), - "AST { source: None, body: [], functions: Module, resolver: None }" + "AST { source: None, body: Block[], functions: Module, resolver: None }" ); engine.set_optimization_level(OptimizationLevel::Full); @@ -82,7 +82,7 @@ fn test_optimizer_parse() -> Result<(), Box> { assert_eq!( format!("{:?}", ast), - "AST { source: None, body: [Expr(42 @ 1:1)], functions: Module, resolver: None }" + "AST { source: None, body: Block[Expr(42 @ 1:1)], functions: Module, resolver: None }" ); Ok(()) diff --git a/tests/var_scope.rs b/tests/var_scope.rs index 082a294b..8efbc5f4 100644 --- a/tests/var_scope.rs +++ b/tests/var_scope.rs @@ -61,7 +61,7 @@ fn test_scope_eval() -> Result<(), Box> { // First invocation engine .eval_with_scope::<()>(&mut scope, " let x = 4 + 5 - y + z; y = 1;") - .expect("y and z not found?"); + .expect("variables y and z should exist"); // Second invocation using the same state let result = engine.eval_with_scope::(&mut scope, "x")?;