Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions crates/message-format/src/compiler/compile/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2775,6 +2775,29 @@ fn optionless_string_reannotation_retains_inherited_universal_id() {
assert_eq!(sink.ids, ["first"]);
}

#[test]
fn explicit_universal_id_overrides_inherited_id() {
let source = ".local $x={foo :custom u:id=first} {{{$x :custom u:id=second}}}";
let bytes = compile_str(source).expect("compiled");
let catalog = Catalog::from_bytes(&bytes).expect("catalog");
let host = HostFn(|_fn_id, args: &[Value], _opts| {
Ok(args
.first()
.cloned()
.unwrap_or_else(|| Value::Str("result".to_string())))
});
let mut formatter = Formatter::new(&catalog, host).expect("formatter");
let message = formatter.resolve("main").expect("message");
let mut sink = UniversalIdSink::default();

formatter
.format_to(message, &[], &mut sink, None)
.expect("formatted");

assert_eq!(sink.output, "foo");
assert_eq!(sink.ids, ["second"]);
}

#[test]
fn universal_id_does_not_change_dynamic_option_values_seen_by_custom_hosts() {
let source = ".local $o={a :custom u:id=opt} {{{a :custom option=$o}}}";
Expand Down
89 changes: 30 additions & 59 deletions crates/message-format/src/runtime/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -751,10 +751,7 @@ impl Host for BuiltinHost {
kind: value.kind,
value: Cow::Borrowed(value.text()),
locale: Some(Cow::Owned(self.locale.to_string())),
id: value
.id
.as_deref()
.or_else(|| value.selection.as_ref()?.id.as_deref()),
id: None,
direction: None,
fields: &[],
});
Expand All @@ -781,7 +778,7 @@ impl Host for BuiltinHost {
kind: FormattedValueKind::Number,
value: Cow::Borrowed(formatted.as_str()),
locale: Some(Cow::Owned(self.locale.to_string())),
id: number.id.as_deref(),
id: None,
direction: None,
fields,
});
Expand Down Expand Up @@ -823,7 +820,6 @@ fn plain_text<'a>(catalog: &'a Catalog, value: &'a Value) -> Cow<'a, str> {
Value::Int(v) => Cow::Owned(v.to_string()),
Value::Float(v) => Cow::Owned(v.to_string()),
Value::Str(v) => Cow::Borrowed(v.as_str()),
Value::Identified { value, .. } => plain_text(catalog, value),
Value::String(v) => Cow::Borrowed(v.text()),
Value::StrRef(id) => catalog
.pool_string_opt(*id)
Expand Down Expand Up @@ -872,7 +868,7 @@ fn format_string(
&& dir.as_deref() == Some("\0inherit")
{
// Compiler-generated default isolation preserves explicit direction
// and identity already established by a stored resolved string.
// already established by a stored resolved string.
let mut resolved = value.clone();
if resolved.direction == StringDirection::Unspecified {
resolved.direction = StringDirection::Auto;
Expand All @@ -886,22 +882,15 @@ fn format_string(
Some("auto" | "\0inherit") => StringDirection::Auto,
Some(_) => StringDirection::Unspecified,
};
let id = options
.get(BuiltinOptionKey::UId)
.map(|value| value.into_owned().into_boxed_str());
if let Value::Int(value) = value {
let text = format_i64(*value);
let mut resolved = ResolvedString::from_integer(text.as_str(), *value, direction);
resolved.id = id;
return resolved;
return ResolvedString::from_integer(text.as_str(), *value, direction);
}
let text = plain_text(catalog, value);
let mut resolved = match text {
match text {
Cow::Borrowed(text) => ResolvedString::from_borrowed(text, direction),
Cow::Owned(text) => ResolvedString::from_owned(text, direction),
};
resolved.id = id;
resolved
}
}

fn value_text<'a>(catalog: &'a Catalog, value: &'a Value) -> Option<&'a str> {
Expand Down Expand Up @@ -937,23 +926,20 @@ fn resolve_number(
on_error: &mut dyn FnMut(MessageFunctionError),
) -> Result<ResolvedNumber, FormatError> {
let value = numeric_source(value);
let (mut number, inherited_format, inherited_selection, inherited_select, inherited_id) =
match value {
Value::Number(number) => (
number.value.clone(),
number.format,
number.selection,
number.has_explicit_select,
number.id.clone(),
),
_ => (
parse_number_value(value, catalog)?,
NumberFormatOptions::DEFAULT,
NumberSelection::None,
false,
None,
),
};
let (mut number, inherited_format, inherited_selection, inherited_select) = match value {
Value::Number(number) => (
number.value.clone(),
number.format,
number.selection,
number.has_explicit_select,
),
_ => (
parse_number_value(value, catalog)?,
NumberFormatOptions::DEFAULT,
NumberSelection::None,
false,
),
};
if integer_only {
// Integer annotations intentionally discard precision inherited from a
// preceding number annotation. The integer function itself emits no
Expand Down Expand Up @@ -1001,34 +987,25 @@ fn resolve_number(
selection => selection,
}
};
let mut resolved = ResolvedNumber::new(number, format, selection, has_explicit_select);
resolved.id = options
.get(BuiltinOptionKey::UId)
.map(|value| value.into_owned().into_boxed_str())
.or(inherited_id);
Ok(resolved)
Ok(ResolvedNumber::new(
number,
format,
selection,
has_explicit_select,
))
}

fn resolve_percent(
value: &Value,
catalog: &Catalog,
options: &EffectiveOptions<'_>,
) -> Result<(ResolvedNumber, ResolvedNumber), FormatError> {
let retained_id = match value {
Value::Formatted(formatted) => formatted.id.clone(),
_ => None,
};
let value = numeric_source(value);
let (number, inherited_format, inherited_id) = match value {
Value::Number(number) => (
number.value.clone(),
number.format,
retained_id.or_else(|| number.id.clone()),
),
let (number, inherited_format) = match value {
Value::Number(number) => (number.value.clone(), number.format),
_ => (
parse_number_value(value, catalog)?,
NumberFormatOptions::DEFAULT,
retained_id,
),
};
let text = match &number {
Expand All @@ -1055,14 +1032,8 @@ fn resolve_percent(
if format.maximum_fraction_digits.is_none() {
format.maximum_fraction_digits = format.minimum_fraction_digits;
}
let id = options
.get(BuiltinOptionKey::UId)
.map(|value| value.into_owned().into_boxed_str())
.or(inherited_id);
let mut source = ResolvedNumber::new(number, format, NumberSelection::Plural, false);
source.id.clone_from(&id);
let mut selection = ResolvedNumber::new(scaled, format, NumberSelection::Plural, false);
selection.id = id;
let source = ResolvedNumber::new(number, format, NumberSelection::Plural, false);
let selection = ResolvedNumber::new(scaled, format, NumberSelection::Plural, false);
Ok((source, selection))
}

Expand Down
2 changes: 1 addition & 1 deletion crates/message-format/src/runtime/formatter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use crate::runtime::{
#[derive(Default)]
pub(crate) struct VmState {
pub(crate) fuel: Option<u64>,
pub(crate) values: Vec<Value>,
pub(crate) values: Vec<super::vm::StoredValue>,
pub(crate) stack: Vec<usize>,
pub(crate) locals: Vec<usize>,
pub(crate) call_args: Vec<Value>,
Expand Down
32 changes: 3 additions & 29 deletions crates/message-format/src/runtime/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,6 @@ pub enum Value {
Float(f64),
/// Owned UTF-8 string.
Str(String),
/// A value carrying the universal `u:id` formatting annotation.
Identified {
/// Underlying function result.
value: Box<Self>,
/// User-provided identifier.
id: String,
},
/// String resolved by the `string` function, retaining direction metadata.
String(ResolvedString),
/// Reference to a catalog string-pool entry.
Expand Down Expand Up @@ -83,7 +76,6 @@ pub struct ResolvedFormatted {
pub(crate) formatted: String,
pub(crate) kind: super::vm::FormattedValueKind,
pub(crate) selection: Option<ResolvedNumber>,
pub(crate) id: Option<Box<str>>,
}

#[cfg(feature = "icu4x")]
Expand All @@ -94,7 +86,6 @@ impl ResolvedFormatted {
formatted,
kind: super::vm::FormattedValueKind::String,
selection: None,
id: None,
}
}

Expand All @@ -104,7 +95,6 @@ impl ResolvedFormatted {
formatted,
kind: super::vm::FormattedValueKind::Number,
selection: None,
id: None,
}
}

Expand All @@ -114,7 +104,6 @@ impl ResolvedFormatted {
formatted,
kind: super::vm::FormattedValueKind::Number,
selection: Some(selection),
id: None,
}
}

Expand All @@ -127,11 +116,7 @@ impl ResolvedFormatted {

impl Value {
pub(crate) fn is_fallback(&self) -> bool {
match self {
Self::Fallback(_) | Self::FunctionFallback(_) => true,
Self::Identified { value, .. } => value.is_fallback(),
_ => false,
}
matches!(self, Self::Fallback(_) | Self::FunctionFallback(_))
}
}

Expand All @@ -142,8 +127,6 @@ pub struct ResolvedString {
text: ResolvedStringText,
/// Direction requested by the string function.
pub(crate) direction: StringDirection,
/// User-provided `u:id`, when present.
pub(crate) id: Option<Box<str>>,
}

const INLINE_STRING_CAPACITY: usize = 24;
Expand Down Expand Up @@ -199,11 +182,7 @@ impl ResolvedString {
} else {
ResolvedStringText::Heap(text.into())
};
Self {
text,
direction,
id: None,
}
Self { text, direction }
}

pub(crate) fn from_owned(text: String, direction: StringDirection) -> Self {
Expand All @@ -213,7 +192,6 @@ impl ResolvedString {
Self {
text: ResolvedStringText::Heap(text.into_boxed_str()),
direction,
id: None,
}
}

Expand All @@ -228,7 +206,6 @@ impl ResolvedString {
bytes,
},
direction,
id: None,
}
}

Expand Down Expand Up @@ -265,7 +242,7 @@ impl ResolvedString {

impl PartialEq for ResolvedString {
fn eq(&self, other: &Self) -> bool {
self.direction == other.direction && self.id == other.id && self.text() == other.text()
self.direction == other.direction && self.text() == other.text()
}
}

Expand Down Expand Up @@ -303,8 +280,6 @@ pub struct ResolvedNumber {
pub(crate) selection: NumberSelection,
/// Whether a `select` option was explicitly resolved for this value.
pub(crate) has_explicit_select: bool,
/// User-provided `u:id`, when present.
pub(crate) id: Option<Box<str>>,
}

/// Exact numeric payload retained by [`ResolvedNumber`].
Expand Down Expand Up @@ -393,7 +368,6 @@ impl ResolvedNumber {
format,
selection,
has_explicit_select,
id: None,
}
}

Expand Down
Loading