mirror of
https://github.com/enso-org/enso.git
synced 2024-11-23 08:08:34 +03:00
Reject @
as binary operator (#4021)
`@` should not be legal to use as a binary operator. I accepted it in the parser because it occurred in the .enso sources, but it was actually used to create a syntax error to test error recovery. See: https://www.pivotaltracker.com/story/show/184054024
This commit is contained in:
parent
f39070abef
commit
591cacb79a
@ -3360,7 +3360,7 @@ class RuntimeServerTest
|
||||
"""from Standard.Base import all
|
||||
|
|
||||
|main =
|
||||
| x = Panic.catch_primitive @ .convert_to_dataflow_error
|
||||
| x = Panic.catch_primitive ` .convert_to_dataflow_error
|
||||
| IO.println x
|
||||
| IO.println (x.catch Any .to_text)
|
||||
|""".stripMargin.linesIterator.mkString("\n")
|
||||
@ -3402,7 +3402,7 @@ class RuntimeServerTest
|
||||
contextId,
|
||||
Seq(
|
||||
Api.ExecutionResult.Diagnostic.error(
|
||||
"Unrecognized token.",
|
||||
"Unexpected expression.",
|
||||
Some(mainFile),
|
||||
Some(model.Range(model.Position(3, 30), model.Position(3, 31)))
|
||||
)
|
||||
@ -3412,8 +3412,8 @@ class RuntimeServerTest
|
||||
context.executionComplete(contextId)
|
||||
)
|
||||
context.consumeOut shouldEqual List(
|
||||
"(Error: (Syntax_Error.Error 'Unrecognized token.'))",
|
||||
"(Syntax_Error.Error 'Unrecognized token.')"
|
||||
"(Error: (Syntax_Error.Error 'Unexpected expression.'))",
|
||||
"(Syntax_Error.Error 'Unexpected expression.')"
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -713,13 +713,6 @@ final class TreeToIr {
|
||||
default -> {
|
||||
var lhs = unnamedCallArgument(app.getLhs());
|
||||
var rhs = unnamedCallArgument(app.getRhs());
|
||||
if ("@".equals(op.codeRepr()) && lhs.value() instanceof IR$Application$Prefix fn) {
|
||||
final Option<IdentifiedLocation> where = getIdentifiedLocation(op);
|
||||
var err = translateSyntaxError(where.get(), IR$Error$Syntax$UnrecognizedToken$.MODULE$);
|
||||
var errArg = new IR$CallArgument$Specified(Option.empty(), err, where, meta(), diag());
|
||||
var args = cons(rhs, cons(errArg, fn.arguments()));
|
||||
yield new IR$Application$Prefix(fn.function(), args.reverse(), false, getIdentifiedLocation(app), meta(), diag());
|
||||
}
|
||||
var name = new IR$Name$Literal(
|
||||
op.codeRepr(), true, getIdentifiedLocation(op), meta(), diag()
|
||||
);
|
||||
|
@ -582,12 +582,11 @@ public class EnsoCompilerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmptyGroup2AndAtSymbol() throws Exception {
|
||||
public void testEmptyGroup2() throws Exception {
|
||||
parseTest("""
|
||||
main =
|
||||
x = ()
|
||||
x = 5
|
||||
y = @
|
||||
""");
|
||||
}
|
||||
|
||||
@ -601,15 +600,6 @@ public class EnsoCompilerTest {
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotAnOperator() throws Exception {
|
||||
parseTest("""
|
||||
main =
|
||||
x = Panic.catch_primitive @ caught_panic-> caught_panic.payload
|
||||
x.to_text
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWildcardLeftHandSide() throws Exception {
|
||||
parseTest("""
|
||||
|
@ -31,10 +31,10 @@ class CompileDiagnosticsTest extends InterpreterTest {
|
||||
|import Standard.Base.Panic.Panic
|
||||
|
|
||||
|main =
|
||||
| x = Panic.catch_primitive @ caught_panic-> caught_panic.payload
|
||||
| x = Panic.catch_primitive ` caught_panic-> caught_panic.payload
|
||||
| x.to_text
|
||||
|""".stripMargin
|
||||
eval(code) shouldEqual "(Syntax_Error.Error 'Unrecognized token.')"
|
||||
eval(code) shouldEqual "(Syntax_Error.Error 'Unexpected expression.')"
|
||||
}
|
||||
|
||||
"surface redefinition errors in the language" in {
|
||||
|
@ -198,14 +198,14 @@ class DataflowErrorsTest extends InterpreterTest {
|
||||
"""from Standard.Base import all
|
||||
|
|
||||
|main =
|
||||
| x = Panic.catch_primitive @ .convert_to_dataflow_error
|
||||
| x = Panic.catch_primitive ` .convert_to_dataflow_error
|
||||
| IO.println x
|
||||
| IO.println (x.catch Any .to_text)
|
||||
|""".stripMargin
|
||||
eval(code)
|
||||
consumeOut shouldEqual List(
|
||||
"(Error: (Syntax_Error.Error 'Unrecognized token.'))",
|
||||
"(Syntax_Error.Error 'Unrecognized token.')"
|
||||
"(Error: (Syntax_Error.Error 'Unexpected expression.'))",
|
||||
"(Syntax_Error.Error 'Unexpected expression.')"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ class StrictCompileDiagnosticsTest extends InterpreterTest {
|
||||
"""main =
|
||||
| x = ()
|
||||
| x = 5
|
||||
| y = @
|
||||
| y = `
|
||||
|""".stripMargin.linesIterator.mkString("\n")
|
||||
the[InterpreterException] thrownBy eval(code) should have message
|
||||
"Compilation aborted due to errors."
|
||||
@ -35,7 +35,7 @@ class StrictCompileDiagnosticsTest extends InterpreterTest {
|
||||
.toSet shouldEqual Set(
|
||||
"Test[2:9-2:10]: Parentheses can't be empty.",
|
||||
"Test[3:5-3:9]: Variable x is being redefined.",
|
||||
"Test[4:9-4:9]: Unrecognized token.",
|
||||
"Test[4:9-4:9]: Unexpected expression.",
|
||||
"Test[4:5-4:5]: Unused variable y.",
|
||||
"Test[2:5-2:5]: Unused variable x."
|
||||
)
|
||||
|
@ -688,20 +688,12 @@ fn unevaluated_argument() {
|
||||
|
||||
#[test]
|
||||
fn unary_operator_missing_operand() {
|
||||
let code = ["main ~ = x"];
|
||||
let expected = block![
|
||||
(Function (Ident main) #((() (UnaryOprApp "~" ()) () ())) "=" (Ident x))
|
||||
];
|
||||
test(&code.join("\n"), expected);
|
||||
test_invalid("main ~ = x");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unary_operator_at_end_of_expression() {
|
||||
let code = ["foo ~"];
|
||||
let expected = block![
|
||||
(App (Ident foo) (UnaryOprApp "~" ()))
|
||||
];
|
||||
test(&code.join("\n"), expected);
|
||||
test_invalid("foo ~");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -1227,8 +1219,8 @@ fn trailing_whitespace() {
|
||||
|
||||
#[test]
|
||||
fn at_operator() {
|
||||
test!("foo@bar", (OprApp (Ident foo) (Ok "@") (Ident bar)));
|
||||
test!("foo @ bar", (OprApp (Ident foo) (Ok "@") (Ident bar)));
|
||||
test_invalid("foo@bar");
|
||||
test_invalid("foo @ bar");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -661,7 +661,6 @@ fn analyze_operator(token: &str) -> token::OperatorProperties {
|
||||
"@" =>
|
||||
return operator
|
||||
.with_unary_prefix_mode(token::Precedence::max())
|
||||
.with_binary_infix_precedence(20)
|
||||
.as_compile_time_operation()
|
||||
.as_annotation(),
|
||||
"-" =>
|
||||
|
@ -209,10 +209,16 @@ impl<'s> ExpressionBuilder<'s> {
|
||||
&mut self,
|
||||
prec: token::Precedence,
|
||||
assoc: token::Associativity,
|
||||
arity: Unary<'s>,
|
||||
mut arity: Unary<'s>,
|
||||
) {
|
||||
if self.prev_type == Some(ItemType::Ast) {
|
||||
self.application();
|
||||
if self.nospace {
|
||||
if let Unary::Simple(token) = arity {
|
||||
let error = "Space required between term and unary-operator expression.".into();
|
||||
arity = Unary::Invalid { token, error };
|
||||
}
|
||||
}
|
||||
}
|
||||
self.push_operator(prec, assoc, Arity::Unary(arity));
|
||||
}
|
||||
@ -277,6 +283,8 @@ impl<'s> ExpressionBuilder<'s> {
|
||||
let ast = match opr.opr {
|
||||
Arity::Unary(Unary::Simple(opr)) =>
|
||||
Operand::from(rhs_).map(|item| syntax::tree::apply_unary_operator(opr, item)),
|
||||
Arity::Unary(Unary::Invalid { token, error }) => Operand::from(rhs_)
|
||||
.map(|item| syntax::tree::apply_unary_operator(token, item).with_error(error)),
|
||||
Arity::Unary(Unary::Fragment { mut fragment }) => {
|
||||
if let Some(rhs_) = rhs_ {
|
||||
fragment.operand(rhs_);
|
||||
@ -331,6 +339,7 @@ impl<'s> ExpressionBuilder<'s> {
|
||||
if child.output.is_empty() && let Some(op) = child.operator_stack.pop() {
|
||||
match op.opr {
|
||||
Arity::Unary(Unary::Simple(un)) => self.operator(un),
|
||||
Arity::Unary(Unary::Invalid{ .. }) => unreachable!(),
|
||||
Arity::Unary(Unary::Fragment{ .. }) => unreachable!(),
|
||||
Arity::Binary { tokens, .. } => tokens.into_iter().for_each(|op| self.operator(op)),
|
||||
};
|
||||
@ -401,6 +410,7 @@ impl<'s> Arity<'s> {
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum Unary<'s> {
|
||||
Simple(token::Operator<'s>),
|
||||
Invalid { token: token::Operator<'s>, error: Cow<'static, str> },
|
||||
Fragment { fragment: ExpressionBuilder<'s> },
|
||||
}
|
||||
|
||||
|
@ -863,6 +863,11 @@ pub fn apply_operator<'s>(
|
||||
}
|
||||
};
|
||||
}
|
||||
if let Ok(opr_) = &opr && !opr_.properties.can_form_section() && lhs.is_none() && rhs.is_none() {
|
||||
let error = format!("Operator `{opr:?}` must be applied to two operands.");
|
||||
let invalid = Tree::opr_app(lhs, opr, rhs);
|
||||
return invalid.with_error(error);
|
||||
}
|
||||
if nospace
|
||||
&& let Ok(opr) = &opr && opr.properties.can_be_decimal_operator()
|
||||
&& let Some(lhs) = lhs.as_mut()
|
||||
@ -898,13 +903,17 @@ pub fn apply_operator<'s>(
|
||||
pub fn apply_unary_operator<'s>(opr: token::Operator<'s>, rhs: Option<Tree<'s>>) -> Tree<'s> {
|
||||
if opr.properties.is_annotation()
|
||||
&& let Some(Tree { variant: box Variant::Ident(Ident { token }), .. }) = rhs {
|
||||
match token.is_type {
|
||||
return match token.is_type {
|
||||
true => Tree::annotated_builtin(opr, token, vec![], None),
|
||||
false => Tree::annotated(opr, token, None, vec![], None),
|
||||
}
|
||||
} else {
|
||||
Tree::unary_opr_app(opr, rhs)
|
||||
};
|
||||
}
|
||||
if !opr.properties.can_form_section() && rhs.is_none() {
|
||||
let error = format!("Operator `{opr:?}` must be applied to an operand.");
|
||||
let invalid = Tree::unary_opr_app(opr, rhs);
|
||||
return invalid.with_error(error);
|
||||
}
|
||||
Tree::unary_opr_app(opr, rhs)
|
||||
}
|
||||
|
||||
/// Create an AST node for a token.
|
||||
|
Loading…
Reference in New Issue
Block a user