Skip to content

Commit 638ce90

Browse files
Fix FluentBundle::format_pattern lifetimes (#264)
* FluentValue::into_owned return a longer living FluentValue, necessary for returning FluentValue from FluentArgs that will outlives FluentArgs * fix lifetimes * add tests * cleaning
1 parent c91d9f4 commit 638ce90

File tree

8 files changed

+132
-65
lines changed

8 files changed

+132
-65
lines changed

fluent-bundle/src/bundle.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -482,10 +482,10 @@ impl<R, M> FluentBundle<R, M> {
482482
///
483483
/// assert_eq!(result, "Hello World!");
484484
/// ```
485-
pub fn format_pattern<'bundle>(
485+
pub fn format_pattern<'bundle, 'args>(
486486
&'bundle self,
487-
pattern: &'bundle ast::Pattern<&str>,
488-
args: Option<&'bundle FluentArgs>,
487+
pattern: &'bundle ast::Pattern<&'bundle str>,
488+
args: Option<&'args FluentArgs>,
489489
errors: &mut Vec<FluentError>,
490490
) -> Cow<'bundle, str>
491491
where

fluent-bundle/src/resolver/expression.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ use crate::resolver::{ResolveValue, ResolverError};
1111
use crate::resource::FluentResource;
1212
use crate::types::FluentValue;
1313

14-
impl<'p> WriteValue for ast::Expression<&'p str> {
15-
fn write<'scope, 'errors, W, R, M>(
16-
&'scope self,
14+
impl<'bundle> WriteValue<'bundle> for ast::Expression<&'bundle str> {
15+
fn write<'ast, 'args, 'errors, W, R, M>(
16+
&'ast self,
1717
w: &mut W,
18-
scope: &mut Scope<'scope, 'errors, R, M>,
18+
scope: &mut Scope<'bundle, 'ast, 'args, 'errors, R, M>,
1919
) -> fmt::Result
2020
where
2121
W: fmt::Write,

fluent-bundle/src/resolver/inline_expression.rs

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ use crate::memoizer::MemoizerKind;
1212
use crate::resource::FluentResource;
1313
use crate::types::FluentValue;
1414

15-
impl<'p> WriteValue for ast::InlineExpression<&'p str> {
16-
fn write<'scope, 'errors, W, R, M>(
17-
&'scope self,
15+
impl<'bundle> WriteValue<'bundle> for ast::InlineExpression<&'bundle str> {
16+
fn write<'ast, 'args, 'errors, W, R, M>(
17+
&'ast self,
1818
w: &mut W,
19-
scope: &mut Scope<'scope, 'errors, R, M>,
19+
scope: &mut Scope<'bundle, 'ast, 'args, 'errors, R, M>,
2020
) -> fmt::Result
2121
where
2222
W: fmt::Write,
@@ -147,11 +147,11 @@ impl<'p> WriteValue for ast::InlineExpression<&'p str> {
147147
}
148148
}
149149

150-
impl<'p> ResolveValue for ast::InlineExpression<&'p str> {
151-
fn resolve<'source, 'errors, R, M>(
152-
&'source self,
153-
scope: &mut Scope<'source, 'errors, R, M>,
154-
) -> FluentValue<'source>
150+
impl<'bundle> ResolveValue<'bundle> for ast::InlineExpression<&'bundle str> {
151+
fn resolve<'ast, 'args, 'errors, R, M>(
152+
&'ast self,
153+
scope: &mut Scope<'bundle, 'ast, 'args, 'errors, R, M>,
154+
) -> FluentValue<'bundle>
155155
where
156156
R: Borrow<FluentResource>,
157157
M: MemoizerKind,
@@ -160,16 +160,18 @@ impl<'p> ResolveValue for ast::InlineExpression<&'p str> {
160160
Self::StringLiteral { value } => unescape_unicode_to_string(value).into(),
161161
Self::NumberLiteral { value } => FluentValue::try_number(*value),
162162
Self::VariableReference { id } => {
163-
let args = scope.local_args.as_ref().or(scope.args);
164-
165-
if let Some(arg) = args.and_then(|args| args.get(id.name)) {
166-
arg.clone()
167-
} else {
168-
if scope.local_args.is_none() {
169-
scope.add_error(self.into());
163+
if let Some(local_args) = &scope.local_args {
164+
if let Some(arg) = local_args.get(id.name) {
165+
return arg.clone();
170166
}
171-
FluentValue::Error
167+
} else if let Some(arg) = scope.args.and_then(|args| args.get(id.name)) {
168+
return arg.into_owned();
169+
}
170+
171+
if scope.local_args.is_none() {
172+
scope.add_error(self.into());
172173
}
174+
FluentValue::Error
173175
}
174176
Self::FunctionReference { id, arguments } => {
175177
let (resolved_positional_args, resolved_named_args) =

fluent-bundle/src/resolver/mod.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,21 @@ use crate::resource::FluentResource;
1515
use crate::types::FluentValue;
1616

1717
// Converts an AST node to a `FluentValue`.
18-
pub(crate) trait ResolveValue {
19-
fn resolve<'source, 'errors, R, M>(
20-
&'source self,
21-
scope: &mut Scope<'source, 'errors, R, M>,
22-
) -> FluentValue<'source>
18+
pub(crate) trait ResolveValue<'bundle> {
19+
fn resolve<'ast, 'args, 'errors, R, M>(
20+
&'ast self,
21+
scope: &mut Scope<'bundle, 'ast, 'args, 'errors, R, M>,
22+
) -> FluentValue<'bundle>
2323
where
2424
R: Borrow<FluentResource>,
2525
M: MemoizerKind;
2626
}
2727

28-
pub(crate) trait WriteValue {
29-
fn write<'source, 'errors, W, R, M>(
30-
&'source self,
28+
pub(crate) trait WriteValue<'bundle> {
29+
fn write<'ast, 'args, 'errors, W, R, M>(
30+
&'ast self,
3131
w: &mut W,
32-
scope: &mut Scope<'source, 'errors, R, M>,
32+
scope: &mut Scope<'bundle, 'ast, 'args, 'errors, R, M>,
3333
) -> fmt::Result
3434
where
3535
W: fmt::Write,

fluent-bundle/src/resolver/pattern.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ use crate::types::FluentValue;
1313

1414
const MAX_PLACEABLES: u8 = 100;
1515

16-
impl<'p> WriteValue for ast::Pattern<&'p str> {
17-
fn write<'scope, 'errors, W, R, M>(
18-
&'scope self,
16+
impl<'bundle> WriteValue<'bundle> for ast::Pattern<&'bundle str> {
17+
fn write<'ast, 'args, 'errors, W, R, M>(
18+
&'ast self,
1919
w: &mut W,
20-
scope: &mut Scope<'scope, 'errors, R, M>,
20+
scope: &mut Scope<'bundle, 'ast, 'args, 'errors, R, M>,
2121
) -> fmt::Result
2222
where
2323
W: fmt::Write,
@@ -80,11 +80,11 @@ impl<'p> WriteValue for ast::Pattern<&'p str> {
8080
}
8181
}
8282

83-
impl<'p> ResolveValue for ast::Pattern<&'p str> {
84-
fn resolve<'source, 'errors, R, M>(
85-
&'source self,
86-
scope: &mut Scope<'source, 'errors, R, M>,
87-
) -> FluentValue<'source>
83+
impl<'bundle> ResolveValue<'bundle> for ast::Pattern<&'bundle str> {
84+
fn resolve<'ast, 'args, 'errors, R, M>(
85+
&'ast self,
86+
scope: &mut Scope<'bundle, 'ast, 'args, 'errors, R, M>,
87+
) -> FluentValue<'bundle>
8888
where
8989
R: Borrow<FluentResource>,
9090
M: MemoizerKind,

fluent-bundle/src/resolver/scope.rs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,28 +8,28 @@ use std::borrow::Borrow;
88
use std::fmt;
99

1010
/// State for a single `ResolveValue::to_value` call.
11-
pub struct Scope<'scope, 'errors, R, M> {
11+
pub struct Scope<'bundle, 'ast, 'args, 'errors, R, M> {
1212
/// The current `FluentBundle` instance.
13-
pub bundle: &'scope FluentBundle<R, M>,
13+
pub bundle: &'bundle FluentBundle<R, M>,
1414
/// The current arguments passed by the developer.
15-
pub(super) args: Option<&'scope FluentArgs<'scope>>,
15+
pub(super) args: Option<&'args FluentArgs<'args>>,
1616
/// Local args
17-
pub(super) local_args: Option<FluentArgs<'scope>>,
17+
pub(super) local_args: Option<FluentArgs<'bundle>>,
1818
/// The running count of resolved placeables. Used to detect the Billion
1919
/// Laughs and Quadratic Blowup attacks.
2020
pub(super) placeables: u8,
2121
/// Tracks hashes to prevent infinite recursion.
22-
travelled: smallvec::SmallVec<[&'scope ast::Pattern<&'scope str>; 2]>,
22+
travelled: smallvec::SmallVec<[&'ast ast::Pattern<&'bundle str>; 2]>,
2323
/// Track errors accumulated during resolving.
2424
pub errors: Option<&'errors mut Vec<FluentError>>,
2525
/// Makes the resolver bail.
2626
pub dirty: bool,
2727
}
2828

29-
impl<'scope, 'errors, R, M> Scope<'scope, 'errors, R, M> {
29+
impl<'bundle, 'ast, 'args, 'errors, R, M> Scope<'bundle, 'ast, 'args, 'errors, R, M> {
3030
pub fn new(
31-
bundle: &'scope FluentBundle<R, M>,
32-
args: Option<&'scope FluentArgs>,
31+
bundle: &'bundle FluentBundle<R, M>,
32+
args: Option<&'args FluentArgs>,
3333
errors: Option<&'errors mut Vec<FluentError>>,
3434
) -> Self {
3535
Scope {
@@ -58,8 +58,8 @@ impl<'scope, 'errors, R, M> Scope<'scope, 'errors, R, M> {
5858
pub fn maybe_track<W>(
5959
&mut self,
6060
w: &mut W,
61-
pattern: &'scope ast::Pattern<&str>,
62-
exp: &'scope ast::Expression<&str>,
61+
pattern: &'ast ast::Pattern<&'bundle str>,
62+
exp: &'ast ast::Expression<&'bundle str>,
6363
) -> fmt::Result
6464
where
6565
R: Borrow<FluentResource>,
@@ -82,8 +82,8 @@ impl<'scope, 'errors, R, M> Scope<'scope, 'errors, R, M> {
8282
pub fn track<W>(
8383
&mut self,
8484
w: &mut W,
85-
pattern: &'scope ast::Pattern<&str>,
86-
exp: &ast::InlineExpression<&str>,
85+
pattern: &'ast ast::Pattern<&'bundle str>,
86+
exp: &'ast ast::InlineExpression<&'bundle str>,
8787
) -> fmt::Result
8888
where
8989
R: Borrow<FluentResource>,
@@ -119,8 +119,8 @@ impl<'scope, 'errors, R, M> Scope<'scope, 'errors, R, M> {
119119

120120
pub fn get_arguments(
121121
&mut self,
122-
arguments: Option<&'scope ast::CallArguments<&'scope str>>,
123-
) -> (Vec<FluentValue<'scope>>, FluentArgs<'scope>)
122+
arguments: Option<&'ast ast::CallArguments<&'bundle str>>,
123+
) -> (Vec<FluentValue<'bundle>>, FluentArgs<'bundle>)
124124
where
125125
R: Borrow<FluentResource>,
126126
M: MemoizerKind,

fluent-bundle/src/types/mod.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,16 @@ impl<'source> FluentValue<'source> {
181181
FluentValue::None => "".into(),
182182
}
183183
}
184+
185+
pub fn into_owned<'a>(&self) -> FluentValue<'a> {
186+
match self {
187+
FluentValue::String(str) => FluentValue::String(Cow::from(str.to_string())),
188+
FluentValue::Number(s) => FluentValue::Number(s.clone()),
189+
FluentValue::Custom(s) => FluentValue::Custom(s.duplicate()),
190+
FluentValue::Error => FluentValue::Error,
191+
FluentValue::None => FluentValue::None,
192+
}
193+
}
184194
}
185195

186196
impl<'source> From<String> for FluentValue<'source> {

fluent-bundle/tests/bundle.rs

Lines changed: 64 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
1-
use fluent_bundle::{FluentBundle, FluentResource};
2-
use unic_langid::LanguageIdentifier;
1+
use fluent_bundle::{FluentArgs, FluentBundle, FluentResource};
2+
use std::borrow::Cow;
3+
use unic_langid::langid;
34

45
#[test]
56
fn add_resource_override() {
67
let res = FluentResource::try_new("key = Value".to_string()).unwrap();
78
let res2 = FluentResource::try_new("key = Value 2".to_string()).unwrap();
89

9-
let en_us: LanguageIdentifier = "en-US"
10-
.parse()
11-
.expect("Failed to parse a language identifier");
10+
let en_us = langid!("en-US");
1211
let mut bundle = FluentBundle::new(vec![en_us]);
1312

1413
bundle.add_resource(&res).expect("Failed to add a resource");
@@ -19,19 +18,75 @@ fn add_resource_override() {
1918

2019
let value = bundle
2120
.get_message("key")
22-
.expect("Failed to retireve a message")
21+
.expect("Failed to retrieve a message")
2322
.value()
24-
.expect("Failed to retireve a value of a message");
23+
.expect("Failed to retrieve a value of a message");
2524
assert_eq!(bundle.format_pattern(value, None, &mut errors), "Value");
2625

2726
bundle.add_resource_overriding(&res2);
2827

2928
let value = bundle
3029
.get_message("key")
31-
.expect("Failed to retireve a message")
30+
.expect("Failed to retrieve a message")
3231
.value()
33-
.expect("Failed to retireve a value of a message");
32+
.expect("Failed to retrieve a value of a message");
3433
assert_eq!(bundle.format_pattern(value, None, &mut errors), "Value 2");
3534

3635
assert!(errors.is_empty());
3736
}
37+
38+
#[test]
39+
fn borrowed_plain_message() {
40+
let res = FluentResource::try_new("key = Value".to_string()).unwrap();
41+
let en_us = langid!("en-US");
42+
43+
let mut bundle = FluentBundle::new(vec![en_us]);
44+
bundle.add_resource(&res).expect("Failed to add a resource");
45+
46+
let mut errors = vec![];
47+
48+
let formatted_pattern = {
49+
let value = bundle
50+
.get_message("key")
51+
.expect("Failed to retrieve a message")
52+
.value()
53+
.expect("Failed to retrieve a value of a message");
54+
55+
bundle.format_pattern(value, None, &mut errors)
56+
};
57+
58+
fn is_borrowed(cow: Cow<'_, str>) -> bool {
59+
match cow {
60+
Cow::Borrowed(_) => true,
61+
_ => false,
62+
}
63+
}
64+
assert_eq!(formatted_pattern, "Value");
65+
assert!(is_borrowed(formatted_pattern));
66+
}
67+
68+
#[test]
69+
fn arguments_outlive_formatted_pattern() {
70+
let res = FluentResource::try_new("key = { $variable }".to_string()).unwrap();
71+
let en_us = langid!("en-US");
72+
73+
let mut bundle = FluentBundle::new(vec![en_us]);
74+
bundle.add_resource(&res).expect("Failed to add a resource");
75+
76+
let mut errors = vec![];
77+
78+
let formatted_pattern = {
79+
let value = bundle
80+
.get_message("key")
81+
.expect("Failed to retrieve a message")
82+
.value()
83+
.expect("Failed to retrieve a value of a message");
84+
85+
let mut args = FluentArgs::new();
86+
args.set("variable", "Variable");
87+
88+
bundle.format_pattern(value, Some(&args), &mut errors)
89+
};
90+
91+
assert_eq!(formatted_pattern, "Variable");
92+
}

0 commit comments

Comments
 (0)