use super::*;
pub(super) fn refresh_this_aliases(
scope: &mut Scope,
aliases: &[Name],
properties: &Arc<ObjectMap>,
) {
let snapshot = Value::Object(Arc::clone(properties), None);
scope.set("this", snapshot.clone());
for alias in aliases {
scope.set(alias, snapshot.clone());
scope.mark_this_alias(alias);
}
}
pub(super) fn release_this_aliases(scope: &mut Scope, aliases: &[Name]) {
for name in std::iter::once("this").chain(aliases.iter().map(|name| &**name)) {
if scope.vars.contains_key(name)
&& let Some(slot) = Arc::make_mut(&mut scope.vars).get_mut(name)
{
*slot = Value::Null;
}
}
}
pub(super) fn module_props_insert(
scope: &mut Scope,
properties: &mut Arc<ObjectMap>,
key: impl Into<Arc<str>>,
value: Value,
) {
for name in ["this", "module"] {
if scope.vars.contains_key(name)
&& let Some(slot) = Arc::make_mut(&mut scope.vars).get_mut(name)
{
*slot = Value::Null;
}
}
Arc::make_mut(properties).insert(key.into(), value);
}
pub(super) fn props_insert(
scope: &mut Scope,
aliases: &[Name],
properties: &mut Arc<ObjectMap>,
key: impl Into<Arc<str>>,
value: Value,
) {
release_this_aliases(scope, aliases);
Arc::make_mut(properties).insert(key.into(), value);
}
pub(super) fn props_shift_insert(
scope: &mut Scope,
aliases: &[Name],
properties: &mut Arc<ObjectMap>,
index: usize,
key: impl Into<Arc<str>>,
value: Value,
) {
release_this_aliases(scope, aliases);
Arc::make_mut(properties).shift_insert(index, key.into(), value);
}
pub(super) fn props_insert_mapping(
scope: &mut Scope,
aliases: &[Name],
properties: &mut Arc<ObjectMap>,
key: Arc<str>,
value: Value,
mapping_entries: bool,
) {
release_this_aliases(scope, aliases);
let properties = Arc::make_mut(properties);
if mapping_entries {
super::insert_mapping_entry(properties, key, value);
} else {
properties.insert(key, value);
}
}
pub(super) fn props_extend_mapping(
scope: &mut Scope,
aliases: &[Name],
properties: &mut Arc<ObjectMap>,
entries: impl Iterator<Item = (Arc<str>, Value)>,
mapping_entries: bool,
) {
release_this_aliases(scope, aliases);
let properties = Arc::make_mut(properties);
for (key, value) in entries {
if mapping_entries {
super::insert_mapping_entry(properties, key, value);
} else {
properties.insert(key, value);
}
}
}
pub(super) fn apply_mapping_entry_template(
template: Option<Value>,
value: Value,
value_type_names: &[String],
scope: &Scope,
) -> Result<Value> {
let merge = |value: Value| match template {
Some(template) => merge_values(template, value),
None => value,
};
let Value::Object(_, Some(value_src)) = &value else {
return Ok(merge(value));
};
let Some(actual) = value_src.type_name.as_deref() else {
return Ok(merge(value));
};
let allowed = expand_type_alias_names(value_type_names, scope)
.into_iter()
.map(|name| mapping_value_type_base(&name).to_string())
.collect::<Vec<_>>();
if allowed.is_empty() {
return Ok(merge(value));
}
if allowed
.iter()
.any(|name| matches!(name.as_str(), "Any" | "Dynamic" | "Object" | "Typed"))
{
return Ok(value);
}
let chain = std::iter::once(actual.to_string())
.chain(value_src.parent_type_names.iter().cloned())
.collect::<Vec<_>>();
let chain = expand_type_alias_names(&chain, scope);
for candidate in &chain {
if allowed.iter().any(|name| type_names_match(name, candidate)) {
return Ok(value);
}
}
let all_known = allowed.iter().all(|name| {
is_object_incompatible_type_name(name)
|| matches!(
resolve_dotted(scope, name),
Some(Value::Object(_, Some(src))) if src.type_name.is_some()
)
});
if all_known {
return Err(Error::Eval(format!(
"Expected value of type `{}`, but got type `{}`.",
allowed.join(" | "),
actual
)));
}
Ok(value)
}
pub(super) fn is_object_incompatible_type_name(name: &str) -> bool {
string_literal_type_value(name).is_some()
|| matches!(
name,
"Null"
| "Boolean"
| "Bool"
| "Int"
| "Int8"
| "Int16"
| "Int32"
| "UInt"
| "UInt8"
| "UInt16"
| "UInt32"
| "Float"
| "Number"
| "String"
| "Duration"
| "DataSize"
| "Regex"
| "Char"
)
}
pub(super) fn expand_type_alias_names(names: &[String], scope: &Scope) -> Vec<String> {
let mut out = Vec::new();
for name in names {
expand_type_alias_name(name, scope, &mut out, 0);
}
out
}
pub(super) fn expand_type_alias_name(
name: &str,
scope: &Scope,
out: &mut Vec<String>,
depth: usize,
) {
let is_default = name.starts_with('*');
let base = name.trim_start_matches('*').trim_end_matches('?');
if depth < 8
&& let Some(alias) = scope.get_type_alias(base)
{
let offset = out.len();
let alias = alias.clone();
collect_type_expr_class_names(&alias, scope, out, depth + 1);
if is_default {
for name in &mut out[offset..] {
if !name.starts_with('*') {
*name = format!("*{name}");
}
}
}
return;
}
let name = if is_default {
format!("*{base}")
} else {
base.to_string()
};
if !out.iter().any(|existing| existing == &name) {
out.push(name);
}
}
pub(super) fn collect_type_expr_class_names(
ty: &crate::parser::TypeExpr,
scope: &Scope,
out: &mut Vec<String>,
depth: usize,
) {
use crate::parser::TypeExpr;
match ty {
TypeExpr::Named(name) => expand_type_alias_name(name, scope, out, depth),
TypeExpr::Constrained(base, _) => expand_type_alias_name(base, scope, out, depth),
TypeExpr::Generic(name, _) => expand_type_alias_name(name, scope, out, depth),
TypeExpr::Nullable(inner) => collect_type_expr_class_names(inner, scope, out, depth),
TypeExpr::Union(variants) => {
for variant in variants {
collect_type_expr_class_names(variant, scope, out, depth);
}
}
}
}
pub(super) fn mapping_value_type_base(name: &str) -> &str {
name.trim_start_matches('*')
}
pub(super) fn select_mapping_type_default<'a>(
type_defaults: &'a [(String, Value)],
body: &[Entry],
) -> Option<&'a (String, Value)> {
if type_defaults.len() <= 1 {
return type_defaults.first();
}
let field_names = body.iter().filter_map(|entry| match entry {
Entry::Property(prop) if !has_modifier(&prop.modifiers, Modifier::Local) => {
Some(prop.name.as_ref())
}
Entry::DynProperty(Expr::String(name), _) => Some(&**name),
_ => None,
});
let mut best = None;
let mut best_score = 0;
for candidate in type_defaults {
let score = field_names
.clone()
.filter(|field| object_declares_field(&candidate.1, field))
.count();
if score > best_score {
best = Some(candidate);
best_score = score;
}
}
best.or_else(|| type_defaults.first())
}
pub(super) fn select_mapping_type_default_for_new<'a>(
type_defaults: &'a [(String, Value)],
type_name: &str,
) -> Option<&'a (String, Value)> {
type_defaults
.iter()
.find(|(name, _)| name == type_name)
.or_else(|| {
type_defaults
.iter()
.find(|(name, _)| type_names_match(name, type_name))
})
.or_else(|| {
type_defaults.iter().find(|(_, value)| {
matches!(
value,
Value::Object(_, Some(src))
if src
.type_name
.as_deref()
.is_some_and(|tn| type_names_match(tn, type_name))
)
})
})
}
pub(super) fn type_names_match(a: &str, b: &str) -> bool {
a == b
|| (a.len() > b.len() && a.ends_with(b) && a.as_bytes()[a.len() - b.len() - 1] == b'.')
|| (b.len() > a.len() && b.ends_with(a) && b.as_bytes()[b.len() - a.len() - 1] == b'.')
}
pub(super) fn merge_partial_module_values(base: &Value, current: &Value) -> Option<Value> {
let (Value::Object(base_map, base_source), Value::Object(current_map, current_source)) =
(base, current)
else {
return None;
};
let mut merged = (**base_map).clone();
merged.extend(
current_map
.iter()
.map(|(name, value)| (name.clone(), value.clone())),
);
Some(Value::Object(
Arc::new(merged),
current_source.clone().or_else(|| base_source.clone()),
))
}
pub(super) fn mapping_entry_body(expr: &Expr) -> Option<&[Entry]> {
match expr {
Expr::ObjectBody(body) => Some(body),
Expr::New(Some(_), body, _) => Some(body),
_ => None,
}
}
pub(super) fn apply_mapping_type_annotation(
value: &mut Value,
type_ann: Option<&crate::parser::TypeExpr>,
) {
let Some(type_ann) = type_ann else {
return;
};
let Value::Object(_, src_slot) = value else {
return;
};
if src_slot.is_none() && is_mapping_type(type_ann) {
*src_slot = Some(Arc::new(mapping_source()));
}
let type_names = mapping_value_type_names(type_ann);
if type_names.is_empty() {
return;
}
if let Some(src) = src_slot
&& type_names
.iter()
.all(|name| src.mapping_value_types.contains(name))
{
return;
}
let src = src_slot.get_or_insert_with(|| Arc::new(mapping_source()));
let src = Arc::make_mut(src);
for name in type_names {
if !src.mapping_value_types.contains(&name) {
src.mapping_value_types.push(name);
}
}
}
pub(super) fn mapping_source() -> ObjectSource {
ObjectSource {
entries: Vec::new().into(),
captured: SourceScope::default(),
body_members: HashSet::default(),
is_open: true,
is_abstract: false,
type_name: None,
type_identity: None,
parent_type_names: Vec::new(),
parent_type_identities: Vec::new(),
entry_scopes: Vec::new(),
evaluated_properties: Vec::new(),
elements: Vec::new(),
mapping_value_types: Vec::new(),
deprecated: IndexMap::new(),
poisoned_members: None,
kind: ObjectKind::Mapping,
is_parsed_json: false,
prototype: None,
}
}
fn is_mapping_type(ty: &crate::parser::TypeExpr) -> bool {
use crate::parser::TypeExpr;
match ty {
TypeExpr::Named(name) | TypeExpr::Generic(name, _) => {
name.trim_start_matches('*').trim_end_matches('?') == "Mapping"
}
TypeExpr::Nullable(inner) => is_mapping_type(inner),
_ => false,
}
}
pub(super) fn mapping_value_type_names(type_ann: &crate::parser::TypeExpr) -> Vec<String> {
use crate::parser::TypeExpr;
let mut names = Vec::new();
match type_ann {
TypeExpr::Generic(name, params) if name == "Mapping" || name == "Map" => {
if let Some(value_type) = params.get(1) {
collect_mapping_value_type_names(value_type, &mut names);
}
}
TypeExpr::Nullable(inner) => return mapping_value_type_names(inner),
TypeExpr::Constrained(base, _) => {
if let Some(value_type) = mapping_value_type_from_name(base) {
names.push(value_type);
}
}
_ => {}
}
names
}
pub(super) fn mapping_value_type_from_name(name: &str) -> Option<String> {
let name = name.trim_start_matches('*').trim_end_matches('?');
let inner = name
.strip_prefix("Mapping<")
.or_else(|| name.strip_prefix("Map<"))?
.strip_suffix('>')?;
let mut depth = 0usize;
for (index, ch) in inner.char_indices() {
match ch {
'<' => depth += 1,
'>' => depth = depth.saturating_sub(1),
',' if depth == 0 => {
let value_type = inner[index + 1..].trim();
return Some(
value_type
.split('<')
.next()
.unwrap_or(value_type)
.trim_end_matches('?')
.to_string(),
);
}
_ => {}
}
}
None
}
pub(super) fn collect_mapping_value_type_names(
type_ann: &crate::parser::TypeExpr,
names: &mut Vec<String>,
) {
use crate::parser::TypeExpr;
match type_ann {
TypeExpr::Named(name) => {
if !names.contains(name) {
names.push(name.clone());
}
}
TypeExpr::Constrained(base, _) => {
if !names.contains(base) {
names.push(base.clone());
}
}
TypeExpr::Generic(name, _) => {
if !names.contains(name) {
names.push(name.clone());
}
}
TypeExpr::Nullable(inner) => collect_mapping_value_type_names(inner, names),
TypeExpr::Union(variants) => {
for variant in variants {
collect_mapping_value_type_names(variant, names);
}
}
}
}
pub(super) fn validate_new_object_body(
type_name: &str,
entries: &[Entry],
base_src: &ObjectSource,
) -> Result<()> {
if base_src.is_open {
return Ok(());
}
let base_names: HashSet<Name> = base_src
.entries
.iter()
.filter_map(|entry| {
if let Entry::Property(prop) = entry {
Some(prop.name.clone())
} else {
None
}
})
.collect();
for entry in entries {
match entry {
Entry::Property(prop)
if !has_modifier(&prop.modifiers, Modifier::Local)
&& !base_names.contains(&prop.name) =>
{
return Err(Error::Eval(format!(
"cannot add property '{}' to non-open class {type_name}",
prop.name
)));
}
Entry::DynProperty(Expr::String(key), _) if !base_names.contains(&**key) => {
return Err(Error::Eval(format!(
"cannot add property '{key}' to non-open class {type_name}"
)));
}
_ => {}
}
}
Ok(())
}
pub(super) fn is_element_only_body(entries: &[Entry]) -> bool {
fn has_members(entries: &[Entry]) -> bool {
entries.iter().any(|entry| match entry {
Entry::DynProperty(..) => true,
Entry::Property(prop) => !has_modifier(&prop.modifiers, Modifier::Local),
Entry::ForGenerator(generator) => has_members(&generator.body),
Entry::WhenGenerator(generator) => {
has_members(&generator.body)
|| generator
.else_body
.as_deref()
.is_some_and(|body| has_members(body))
}
_ => false,
})
}
entries_are_listing_amendment(entries) && !has_members(entries)
}
pub(super) fn find_listing_body_property(entries: &[Entry]) -> Option<&str> {
entries.iter().find_map(|entry| match entry {
Entry::Property(prop)
if &*prop.name != "default" && !has_modifier(&prop.modifiers, Modifier::Local) =>
{
Some(prop.name.as_ref())
}
Entry::ForGenerator(generator) => find_listing_body_property(&generator.body),
Entry::WhenGenerator(generator) => {
find_listing_body_property(&generator.body).or_else(|| {
generator
.else_body
.as_deref()
.and_then(|body| find_listing_body_property(body))
})
}
_ => None,
})
}
pub(super) fn find_default_body_entries(entries: &[Entry]) -> Option<crate::parser::Body> {
entries.iter().find_map(|entry| {
if let Entry::Property(prop) = entry
&& &*prop.name == "default"
&& !has_modifier(&prop.modifiers, Modifier::Local)
{
return prop.body.clone();
}
None
})
}
pub(super) fn object_declares_field(value: &Value, field: &str) -> bool {
let Value::Object(map, source) = value else {
return false;
};
map.contains_key(field)
|| source.as_ref().is_some_and(|source| {
source.entries.iter().any(|entry| match entry {
Entry::Property(prop) => &*prop.name == field,
Entry::DynProperty(Expr::String(name), _) => **name == *field,
_ => false,
})
})
}