Skip to content

Commit f620cf8

Browse files
authored
Allow const expressions in all settings (#3037)
1 parent e2992ea commit f620cf8

13 files changed

Lines changed: 366 additions & 168 deletions

README.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1054,11 +1054,12 @@ Which is equivalent to:
10541054
set NAME := true
10551055
```
10561056

1057-
The `working-directory` setting can be set to a string, or an
1058-
expression<sup>master</sup>. Because the `working-directory` setting affects
1059-
backtick execution and the behavior of many functions, the expression may not
1060-
contain backticks, function calls, or references to variable assignments which
1061-
themselves, directly or transitively, contain backticks or function calls.
1057+
Non-boolean settings can be set to both strings and
1058+
expressions.<sup>master</sup>
1059+
1060+
However, because settings affect the behavior of backticks and many functions,
1061+
those expressions may not contain backticks or function calls, directly or
1062+
transitively via reference.
10621063

10631064
#### Allow Duplicate Recipes
10641065

src/analyzer.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ impl<'run, 'src> Analyzer<'run, 'src> {
143143
AssignmentResolver::resolve_assignments(&assignments)?;
144144

145145
for set in self.sets.values() {
146-
if let Some(expression) = set.value.expression() {
146+
for expression in set.value.expressions() {
147147
for variable in expression.variables() {
148148
let name = variable.lexeme();
149149
if !assignments.contains_key(name) && !constants().contains_key(name) {

src/attribute.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ pub(crate) enum Attribute<'src> {
4343
Parallel,
4444
PositionalArguments,
4545
Private,
46-
Script(Option<Interpreter>),
46+
Script(Option<Interpreter<StringLiteral>>),
4747
Unix,
4848
Windows,
4949
WorkingDirectory(StringLiteral),

src/evaluator.rs

Lines changed: 54 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -74,72 +74,86 @@ impl<'src, 'run> Evaluator<'src, 'run> {
7474

7575
for (_name, set) in sets {
7676
match set.value {
77-
Setting::AllowDuplicateRecipes(allow_duplicate_recipes) => {
78-
settings.allow_duplicate_recipes = allow_duplicate_recipes;
77+
Setting::AllowDuplicateRecipes(value) => {
78+
settings.allow_duplicate_recipes = value;
7979
}
80-
Setting::AllowDuplicateVariables(allow_duplicate_variables) => {
81-
settings.allow_duplicate_variables = allow_duplicate_variables;
80+
Setting::AllowDuplicateVariables(value) => {
81+
settings.allow_duplicate_variables = value;
8282
}
83-
Setting::DotenvFilename(filename) => {
84-
settings.dotenv_filename = Some(filename.cooked);
83+
Setting::DotenvFilename(value) => {
84+
settings.dotenv_filename = Some(self.evaluate_expression(&value)?);
8585
}
86-
Setting::DotenvLoad(dotenv_load) => {
87-
settings.dotenv_load = dotenv_load;
86+
Setting::DotenvLoad(value) => {
87+
settings.dotenv_load = value;
8888
}
89-
Setting::DotenvPath(path) => {
90-
settings.dotenv_path = Some(path.cooked.into());
89+
Setting::DotenvPath(value) => {
90+
settings.dotenv_path = Some(self.evaluate_expression(&value)?.into());
9191
}
92-
Setting::DotenvOverride(dotenv_overrride) => {
93-
settings.dotenv_override = dotenv_overrride;
92+
Setting::DotenvOverride(value) => {
93+
settings.dotenv_override = value;
9494
}
95-
Setting::DotenvRequired(dotenv_required) => {
96-
settings.dotenv_required = dotenv_required;
95+
Setting::DotenvRequired(value) => {
96+
settings.dotenv_required = value;
9797
}
98-
Setting::Export(export) => {
99-
settings.export = export;
98+
Setting::Export(value) => {
99+
settings.export = value;
100100
}
101-
Setting::Fallback(fallback) => {
102-
settings.fallback = fallback;
101+
Setting::Fallback(value) => {
102+
settings.fallback = value;
103103
}
104-
Setting::IgnoreComments(ignore_comments) => {
105-
settings.ignore_comments = ignore_comments;
104+
Setting::IgnoreComments(value) => {
105+
settings.ignore_comments = value;
106106
}
107-
Setting::NoExitMessage(no_exit_message) => {
108-
settings.no_exit_message = no_exit_message;
107+
Setting::NoExitMessage(value) => {
108+
settings.no_exit_message = value;
109109
}
110-
Setting::PositionalArguments(positional_arguments) => {
111-
settings.positional_arguments = positional_arguments;
110+
Setting::PositionalArguments(value) => {
111+
settings.positional_arguments = value;
112112
}
113-
Setting::Quiet(quiet) => {
114-
settings.quiet = quiet;
113+
Setting::Quiet(value) => {
114+
settings.quiet = value;
115115
}
116-
Setting::ScriptInterpreter(script_interpreter) => {
117-
settings.script_interpreter = Some(script_interpreter);
116+
Setting::ScriptInterpreter(value) => {
117+
settings.script_interpreter = Some(self.evaluate_intepreter(&value)?);
118118
}
119-
Setting::Shell(shell) => {
120-
settings.shell = Some(shell);
119+
Setting::Shell(value) => {
120+
settings.shell = Some(self.evaluate_intepreter(&value)?);
121121
}
122-
Setting::Unstable(unstable) => {
123-
settings.unstable = unstable;
122+
Setting::Unstable(value) => {
123+
settings.unstable = value;
124124
}
125-
Setting::WindowsPowerShell(windows_powershell) => {
126-
settings.windows_powershell = windows_powershell;
125+
Setting::WindowsPowerShell(value) => {
126+
settings.windows_powershell = value;
127127
}
128-
Setting::WindowsShell(windows_shell) => {
129-
settings.windows_shell = Some(windows_shell);
128+
Setting::WindowsShell(value) => {
129+
settings.windows_shell = Some(self.evaluate_intepreter(&value)?);
130130
}
131-
Setting::Tempdir(tempdir) => {
132-
settings.tempdir = Some(tempdir.cooked);
131+
Setting::Tempdir(value) => {
132+
settings.tempdir = Some(self.evaluate_expression(&value)?);
133133
}
134-
Setting::WorkingDirectory(working_directory) => {
135-
settings.working_directory = Some(self.evaluate_expression(&working_directory)?.into());
134+
Setting::WorkingDirectory(value) => {
135+
settings.working_directory = Some(self.evaluate_expression(&value)?.into());
136136
}
137137
}
138138
}
139139

140140
Ok(settings)
141141
}
142142

143+
pub(crate) fn evaluate_intepreter(
144+
&mut self,
145+
interpreter: &Interpreter<Expression<'src>>,
146+
) -> RunResult<'src, Interpreter<String>> {
147+
Ok(Interpreter {
148+
command: self.evaluate_expression(&interpreter.command)?,
149+
arguments: interpreter
150+
.arguments
151+
.iter()
152+
.map(|argument| self.evaluate_expression(argument))
153+
.collect::<RunResult<Vec<String>>>()?,
154+
})
155+
}
156+
143157
pub(crate) fn evaluate_assignments(
144158
config: &'run Config,
145159
dotenv: &'run BTreeMap<String, String>,

src/executor.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use super::*;
22

33
pub(crate) enum Executor<'a> {
4-
Command(&'a Interpreter),
4+
Command(Interpreter<String>),
55
Shebang(Shebang<'a>),
66
}
77

@@ -15,14 +15,14 @@ impl Executor<'_> {
1515
) -> RunResult<'src, Command> {
1616
match self {
1717
Self::Command(interpreter) => {
18-
let mut command = Command::new(&interpreter.command.cooked);
18+
let mut command = Command::new(&interpreter.command);
1919

2020
if let Some(working_directory) = working_directory {
2121
command.current_dir(working_directory);
2222
}
2323

2424
for arg in &interpreter.arguments {
25-
command.arg(&arg.cooked);
25+
command.arg(arg);
2626
}
2727

2828
command.arg(path);
@@ -50,7 +50,7 @@ impl Executor<'_> {
5050
pub(crate) fn script_filename(&self, recipe: &str, extension: Option<&str>) -> String {
5151
let extension = extension.unwrap_or_else(|| {
5252
let interpreter = match self {
53-
Self::Command(interpreter) => &interpreter.command.cooked,
53+
Self::Command(interpreter) => &interpreter.command,
5454
Self::Shebang(shebang) => shebang.interpreter_filename(),
5555
};
5656

@@ -67,11 +67,11 @@ impl Executor<'_> {
6767
pub(crate) fn error<'src>(&self, io_error: io::Error, recipe: &'src str) -> Error<'src> {
6868
match self {
6969
Self::Command(Interpreter { command, arguments }) => {
70-
let mut command = command.cooked.clone();
70+
let mut command = command.clone();
7171

7272
for arg in arguments {
7373
command.push(' ');
74-
command.push_str(&arg.cooked);
74+
command.push_str(arg);
7575
}
7676

7777
Error::Script {
@@ -139,8 +139,8 @@ mod tests {
139139
expected
140140
);
141141
assert_eq!(
142-
Executor::Command(&Interpreter {
143-
command: StringLiteral::from_raw(interpreter),
142+
Executor::Command(Interpreter {
143+
command: interpreter.into(),
144144
arguments: Vec::new()
145145
})
146146
.script_filename(recipe, extension),

src/interpreter.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
use super::*;
22

33
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize)]
4-
pub(crate) struct Interpreter {
5-
pub(crate) arguments: Vec<StringLiteral>,
6-
pub(crate) command: StringLiteral,
4+
pub(crate) struct Interpreter<T> {
5+
pub(crate) arguments: Vec<T>,
6+
pub(crate) command: T,
77
}
88

9-
impl Interpreter {
10-
pub(crate) fn default_script_interpreter() -> &'static Interpreter {
11-
static INSTANCE: LazyLock<Interpreter> = LazyLock::new(|| Interpreter {
12-
arguments: vec![StringLiteral::from_raw("-eu")],
13-
command: StringLiteral::from_raw("sh"),
9+
impl Interpreter<String> {
10+
pub(crate) fn default_script_interpreter() -> &'static Self {
11+
static INSTANCE: LazyLock<Interpreter<String>> = LazyLock::new(|| Interpreter::<String> {
12+
arguments: vec!["-eu".into()],
13+
command: "sh".into(),
1414
});
1515
&INSTANCE
1616
}
1717
}
1818

19-
impl Display for Interpreter {
19+
impl<T: Display> Display for Interpreter<T> {
2020
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
2121
write!(f, "{}", self.command)?;
2222

src/node.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -325,20 +325,20 @@ impl<'src> Node<'src> for Set<'src> {
325325
| Setting::IgnoreComments(value) => {
326326
set.push_mut(value.to_string());
327327
}
328+
Setting::DotenvFilename(value)
329+
| Setting::DotenvPath(value)
330+
| Setting::Tempdir(value)
331+
| Setting::WorkingDirectory(value) => {
332+
set.push_mut(value.tree());
333+
}
328334
Setting::ScriptInterpreter(Interpreter { command, arguments })
329335
| Setting::Shell(Interpreter { command, arguments })
330336
| Setting::WindowsShell(Interpreter { command, arguments }) => {
331-
set.push_mut(Tree::string(&command.cooked));
337+
set.push_mut(command.tree());
332338
for argument in arguments {
333-
set.push_mut(Tree::string(&argument.cooked));
339+
set.push_mut(argument.tree());
334340
}
335341
}
336-
Setting::DotenvFilename(value) | Setting::DotenvPath(value) | Setting::Tempdir(value) => {
337-
set.push_mut(Tree::string(&value.cooked));
338-
}
339-
Setting::WorkingDirectory(value) => {
340-
set.push_mut(value.tree());
341-
}
342342
}
343343

344344
set

src/parser.rs

Lines changed: 13 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1351,11 +1351,11 @@ impl<'run, 'src> Parser<'run, 'src> {
13511351
self.expect(ColonEquals)?;
13521352

13531353
let set_value = match keyword {
1354-
Keyword::DotenvFilename => Some(Setting::DotenvFilename(self.parse_string_literal()?)),
1355-
Keyword::DotenvPath => Some(Setting::DotenvPath(self.parse_string_literal()?)),
1354+
Keyword::DotenvFilename => Some(Setting::DotenvFilename(self.parse_expression()?)),
1355+
Keyword::DotenvPath => Some(Setting::DotenvPath(self.parse_expression()?)),
13561356
Keyword::ScriptInterpreter => Some(Setting::ScriptInterpreter(self.parse_interpreter()?)),
13571357
Keyword::Shell => Some(Setting::Shell(self.parse_interpreter()?)),
1358-
Keyword::Tempdir => Some(Setting::Tempdir(self.parse_string_literal()?)),
1358+
Keyword::Tempdir => Some(Setting::Tempdir(self.parse_expression()?)),
13591359
Keyword::WindowsShell => Some(Setting::WindowsShell(self.parse_interpreter()?)),
13601360
Keyword::WorkingDirectory => Some(Setting::WorkingDirectory(self.parse_expression()?)),
13611361
_ => None,
@@ -1371,16 +1371,16 @@ impl<'run, 'src> Parser<'run, 'src> {
13711371
}
13721372

13731373
/// Parse interpreter setting value, i.e., `['sh', '-eu']`
1374-
fn parse_interpreter(&mut self) -> CompileResult<'src, Interpreter> {
1374+
fn parse_interpreter(&mut self) -> CompileResult<'src, Interpreter<Expression<'src>>> {
13751375
self.expect(BracketL)?;
13761376

1377-
let command = self.parse_string_literal()?;
1377+
let command = self.parse_expression()?;
13781378

13791379
let mut arguments = Vec::new();
13801380

13811381
if self.accepted(Comma)? {
13821382
while !self.next_is(BracketR) {
1383-
arguments.push(self.parse_string_literal()?);
1383+
arguments.push(self.parse_expression()?);
13841384

13851385
if !self.accepted(Comma)? {
13861386
break;
@@ -2921,39 +2921,16 @@ mod tests {
29212921
width: 1,
29222922
kind: UnexpectedToken {
29232923
expected: vec![
2924+
Backtick,
29242925
Identifier,
2926+
ParenL,
2927+
Slash,
29252928
StringToken,
29262929
],
29272930
found: BracketR,
29282931
},
29292932
}
29302933

2931-
error! {
2932-
name: set_shell_non_literal_first,
2933-
input: "set shell := ['bar' + 'baz']",
2934-
offset: 20,
2935-
line: 0,
2936-
column: 20,
2937-
width: 1,
2938-
kind: UnexpectedToken {
2939-
expected: vec![BracketR, Comma],
2940-
found: Plus,
2941-
},
2942-
}
2943-
2944-
error! {
2945-
name: set_shell_non_literal_second,
2946-
input: "set shell := ['biz', 'bar' + 'baz']",
2947-
offset: 27,
2948-
line: 0,
2949-
column: 27,
2950-
width: 1,
2951-
kind: UnexpectedToken {
2952-
expected: vec![BracketR, Comma],
2953-
found: Plus,
2954-
},
2955-
}
2956-
29572934
error! {
29582935
name: set_shell_bad_comma,
29592936
input: "set shell := ['bash',",
@@ -2963,8 +2940,11 @@ mod tests {
29632940
width: 0,
29642941
kind: UnexpectedToken {
29652942
expected: vec![
2943+
Backtick,
29662944
BracketR,
29672945
Identifier,
2946+
ParenL,
2947+
Slash,
29682948
StringToken,
29692949
],
29702950
found: Eof,
@@ -2979,7 +2959,7 @@ mod tests {
29792959
column: 20,
29802960
width: 0,
29812961
kind: UnexpectedToken {
2982-
expected: vec![BracketR, Comma],
2962+
expected: vec![AmpersandAmpersand, BarBar, BracketR, Comma, Plus, Slash],
29832963
found: Eof,
29842964
},
29852965
}

0 commit comments

Comments
 (0)