Autoscope syntax (#9372)

Add autoscope syntax (`..Ident`).

# Important Notes
- Also rename previous `Tree.Autoscope` to `SuspendedDefaultArguments`.
This commit is contained in:
Kaz Wesley 2024-03-12 15:31:16 -04:00 committed by GitHub
parent d9ca6cf023
commit a1c0d9ac08
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 92 additions and 55 deletions

View File

@ -615,7 +615,7 @@ final class TreeToIr {
var tree = ast;
for (;;) {
switch (tree) {
case Tree.App app when app.getArg() instanceof Tree.AutoScope -> {
case Tree.App app when app.getArg() instanceof Tree.SuspendedDefaultArguments -> {
hasDefaultsSuspended = true;
tree = app.getFunc();
}
@ -670,16 +670,6 @@ final class TreeToIr {
var loc = getIdentifiedLocation(oprApp.getLhs());
args.add(new CallArgument.Specified(Option.empty(), self, loc, meta(), diag()));
}
} else if (tree instanceof Tree.OprApp oprApp
&& isDotDotOperator(oprApp.getOpr().getRight())
&& oprApp.getRhs() instanceof Tree.Ident ident) {
var methodName = buildName(ident);
func = new Name.MethodReference(
Option.empty(),
methodName,
methodName.location(),
meta(), diag()
);
} else if (args.isEmpty()) {
return null;
} else {
@ -1064,12 +1054,21 @@ final class TreeToIr {
case Tree.App app -> {
var fn = translateExpression(app.getFunc(), isMethod);
var loc = getIdentifiedLocation(app);
if (app.getArg() instanceof Tree.AutoScope) {
if (app.getArg() instanceof Tree.SuspendedDefaultArguments) {
yield new Application.Prefix(fn, nil(), true, loc, meta(), diag());
} else {
yield fn.setLocation(loc);
}
}
case Tree.AutoscopedIdentifier autoscopedIdentifier -> {
var methodName = buildName(autoscopedIdentifier.getIdent());
yield new Name.MethodReference(
Option.empty(),
methodName,
methodName.location(),
meta(), diag()
);
}
case Tree.Invalid __ -> translateSyntaxError(tree, Syntax.UnexpectedExpression$.MODULE$);
default -> translateSyntaxError(tree, new Syntax.UnsupportedSyntax("translateExpression"));
};
@ -1103,7 +1102,7 @@ final class TreeToIr {
case Tree.BodyBlock ignored -> null;
case Tree.Number ignored -> null;
case Tree.Wildcard ignored -> null;
case Tree.AutoScope ignored -> null;
case Tree.SuspendedDefaultArguments ignored -> null;
case Tree.ForeignFunction ignored -> null;
case Tree.Import ignored -> null;
case Tree.Export ignored -> null;
@ -1880,10 +1879,6 @@ final class TreeToIr {
return op != null && ".".equals(op.codeRepr());
}
private static boolean isDotDotOperator(Token.Operator op) {
return op != null && "..".equals(op.codeRepr());
}
private static Tree maybeManyParensed(Tree t) {
for (;;) {
switch (t) {

View File

@ -934,7 +934,7 @@ public class EnsoParserTest {
}
@Test
public void testAutoScope() throws Exception {
public void testSuspendedDefaultArguments() throws Exception {
parseTest("""
fn that_meta =
c_2 = that_meta.constructor ...
@ -942,7 +942,7 @@ public class EnsoParserTest {
}
@Test
public void testAutoScope2() throws Exception {
public void testSuspendedDefaultArguments2() throws Exception {
parseTest("""
fn1 = fn ...
fn2 = fn 1 ...

View File

@ -49,7 +49,7 @@ where T: serde::Serialize + Reflect {
vec![Digits::reflect(), NumberBase::reflect(), Operator::reflect(), TextSection::reflect()];
let stringish_tokens = stringish_tokens.into_iter().map(|t| rust_to_meta[&t.id]);
let skip_tokens = vec![
AutoScope::reflect(),
SuspendedDefaultArguments::reflect(),
CloseSymbol::reflect(),
Newline::reflect(),
OpenSymbol::reflect(),

View File

@ -865,6 +865,25 @@ fn method_app_in_minus_unary() {
(UnaryOprApp "-" (OprApp (Ident Number) (Ok ".") (Ident positive_infinity))));
}
#[test]
fn autoscope_operator() {
test!("x : ..True", (TypeSignature (Ident x) ":" (AutoscopedIdentifier ".." True)));
test!("x = ..True", (Assignment (Ident x) "=" (AutoscopedIdentifier ".." True)));
test!("x = f ..True",
(Assignment (Ident x) "=" (App (Ident f) (AutoscopedIdentifier ".." True))));
expect_invalid_node("x = ..not_a_constructor");
expect_invalid_node("x = case a of ..True -> True");
expect_invalid_node("x = ..4");
expect_invalid_node("x = ..Foo.Bar");
expect_invalid_node("x = f .. True");
expect_invalid_node("x = f(.. ..)");
expect_invalid_node("x = f(.. *)");
expect_invalid_node("x = f(.. True)");
expect_multiple_operator_error("x = ..");
expect_multiple_operator_error("x = .. True");
expect_multiple_operator_error("x : .. True");
}
// === Import/Export ===
@ -1005,7 +1024,10 @@ fn type_annotations() {
(App (Ident foo)
(Group (TypeAnnotated (Ident x) ":" (Ident Int)))))]),
("(x : My_Type _)", block![
(Group (TypeAnnotated (Ident x) ":" (App (Ident My_Type) (Wildcard -1))))]),
(Group
(TypeAnnotated (Ident x)
":"
(App (Ident My_Type) (TemplateFunction 1 (Wildcard 0)))))]),
("x : List Int -> Int", block![
(TypeSignature (Ident x) ":"
(OprApp (App (Ident List) (Ident Int)) (Ok "->") (Ident Int)))]),
@ -1219,8 +1241,13 @@ fn case_expression() {
(CaseOf (Ident foo) #(
((() (TypeAnnotated (Ident v) ":" (Ident My_Type)) "->" (Ident x)))
((() (TypeAnnotated (Ident v) ":"
(Group (App (App (Ident My_Type) (Wildcard -1)) (Wildcard -1))))
"->" (Ident x)))))];
(Group (App
(App
(Ident My_Type)
(TemplateFunction 1 (Wildcard 0)))
(TemplateFunction 1 (Wildcard 0)))))
"->" (Ident x)))))
];
test(&code.join("\n"), expected);
}
@ -1269,7 +1296,7 @@ fn case_by_type() {
}
#[test]
fn pattern_match_auto_scope() {
fn pattern_match_suspended_default_arguments() {
#[rustfmt::skip]
let code = [
"case self of",
@ -1277,7 +1304,7 @@ fn pattern_match_auto_scope() {
];
#[rustfmt::skip]
let expected = block![
(CaseOf (Ident self) #(((() (App (Ident Vector_2d) (AutoScope)) "->" (Ident x)))))];
(CaseOf (Ident self) #(((() (App (Ident Vector_2d) (SuspendedDefaultArguments)) "->" (Ident x)))))];
test(&code.join("\n"), expected);
}

View File

@ -652,7 +652,7 @@ impl<'s> Lexer<'s> {
}
// Composed of operator characters, but not an operator node.
"..." => {
let token = token.with_variant(token::Variant::auto_scope());
let token = token.with_variant(token::Variant::suspended_default_arguments());
self.submit_token(token);
}
// Decimal vs. method-application must be distinguished before parsing because they
@ -714,6 +714,11 @@ fn analyze_operator(token: &str) -> token::OperatorProperties {
.with_unary_prefix_mode(token::Precedence::max())
.as_compile_time_operation()
.as_suspension(),
".." =>
return operator
.with_unary_prefix_mode(token::Precedence::min_valid())
.as_compile_time_operation()
.as_autoscope(),
"@" =>
return operator
.with_unary_prefix_mode(token::Precedence::max())

View File

@ -300,26 +300,6 @@ fn is_qualified_name(tree: &syntax::Tree) -> bool {
}
}
fn expression_to_type(mut input: syntax::Tree<'_>) -> syntax::Tree<'_> {
use syntax::tree::*;
if let Variant::Wildcard(wildcard) = &mut *input.variant {
wildcard.de_bruijn_index = None;
return input;
}
let mut out = match input.variant {
box Variant::TemplateFunction(TemplateFunction { ast, .. }) => expression_to_type(ast),
box Variant::Group(Group { open, body: Some(body), close }) =>
Tree::group(open, Some(expression_to_type(body)), close),
box Variant::OprApp(OprApp { lhs, opr, rhs }) =>
Tree::opr_app(lhs.map(expression_to_type), opr, rhs.map(expression_to_type)),
box Variant::App(App { func, arg }) =>
Tree::app(expression_to_type(func), expression_to_type(arg)),
_ => return input,
};
out.span.left_offset += input.span.left_offset;
out
}
fn expression_to_pattern(mut input: syntax::Tree<'_>) -> syntax::Tree<'_> {
use syntax::tree::*;
if let Variant::Wildcard(wildcard) = &mut *input.variant {
@ -334,6 +314,8 @@ fn expression_to_pattern(mut input: syntax::Tree<'_>) -> syntax::Tree<'_> {
Tree::app(expression_to_pattern(func), expression_to_pattern(arg)),
box Variant::TypeAnnotated(TypeAnnotated { expression, operator, type_ }) =>
Tree::type_annotated(expression_to_pattern(expression), operator, type_),
box Variant::AutoscopedIdentifier(_) =>
return input.with_error("The autoscope operator (..) cannot be used in a pattern."),
_ => return input,
};
out.span.left_offset += input.span.left_offset;

View File

@ -259,7 +259,7 @@ macro_rules! with_token_definition { ($f:ident ($($args:tt)*)) => { $f! { $($arg
Wildcard {
pub lift_level: u32
},
AutoScope,
SuspendedDefaultArguments,
Ident {
pub is_free: bool,
pub lift_level: u32,
@ -340,6 +340,7 @@ pub struct OperatorProperties {
is_arrow: bool,
is_sequence: bool,
is_suspension: bool,
is_autoscope: bool,
is_annotation: bool,
is_dot: bool,
is_special: bool,
@ -427,6 +428,11 @@ impl OperatorProperties {
Self { is_suspension: true, ..self }
}
/// Return a copy of this operator, modified to be flagged as the autoscope operator.
pub fn as_autoscope(self) -> Self {
Self { is_autoscope: true, ..self }
}
/// Return a copy of this operator, modified to be flagged as the dot operator.
pub fn as_dot(self) -> Self {
Self { is_dot: true, ..self }
@ -492,6 +498,11 @@ impl OperatorProperties {
self.is_suspension
}
/// Return whether this operator is the autoscope operator.
pub fn is_autoscope(&self) -> bool {
self.is_autoscope
}
/// Return whether this operator is the annotation operator.
pub fn is_annotation(&self) -> bool {
self.is_annotation

View File

@ -118,9 +118,9 @@ macro_rules! with_ast_definition { ($f:ident ($($args:tt)*)) => { $f! { $($args)
#[reflect(as = "i32")]
pub de_bruijn_index: Option<u32>,
},
/// The auto-scoping marker, `...`.
AutoScope {
pub token: token::AutoScope<'s>,
/// The suspended-default-arguments marker, `...`.
SuspendedDefaultArguments {
pub token: token::SuspendedDefaultArguments<'s>,
},
TextLiteral {
pub open: Option<token::TextStart<'s>>,
@ -162,6 +162,11 @@ macro_rules! with_ast_definition { ($f:ident ($($args:tt)*)) => { $f! { $($args)
pub opr: token::Operator<'s>,
pub rhs: Option<Tree<'s>>,
},
/// Application of the autoscope operator to an identifier, e.g. `..True`.
AutoscopedIdentifier {
pub opr: token::Operator<'s>,
pub ident: token::Ident<'s>,
},
/// Defines the point where operator sections should be expanded to lambdas. Let's consider
/// the expression `map (.sum 1)`. It should be desugared to `map (x -> x.sum 1)`, not to
/// `map ((x -> x.sum) 1)`. The expression `.sum` will be parsed as operator section
@ -897,10 +902,7 @@ pub fn apply_operator<'s>(
}
if let Ok(opr_) = &opr && opr_.properties.is_type_annotation() {
return match (lhs, rhs) {
(Some(lhs), Some(rhs)) => {
let rhs = crate::expression_to_type(rhs);
Tree::type_annotated(lhs, opr.unwrap(), rhs)
},
(Some(lhs), Some(rhs)) => Tree::type_annotated(lhs, opr.unwrap(), rhs),
(lhs, rhs) => {
let invalid = Tree::opr_app(lhs, opr, rhs);
invalid.with_error("`:` operator must be applied to two operands.")
@ -951,6 +953,21 @@ pub fn apply_unary_operator<'s>(opr: token::Operator<'s>, rhs: Option<Tree<'s>>)
false => Tree::annotated(opr, token, None, vec![], None),
};
}
if opr.properties.is_autoscope() && let Some(rhs) = rhs {
return if let box Variant::Ident(Ident { mut token }) = rhs.variant {
let applied_to_type = token.variant.is_type;
token.left_offset = rhs.span.left_offset;
let autoscope_application = Tree::autoscoped_identifier(opr, token);
return if applied_to_type {
autoscope_application
} else {
autoscope_application
.with_error("The auto-scope operator may only be applied to a capitalized identifier.")
}
} else {
Tree::unary_opr_app(opr, Some(rhs)).with_error("The auto-scope operator (..) may only be applied to an identifier.")
}
}
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);
@ -990,7 +1007,7 @@ pub fn to_ast(token: Token) -> Tree {
Tree::text_literal(default(), default(), vec![newline], default(), default())
}
token::Variant::Wildcard(wildcard) => Tree::wildcard(token.with_variant(wildcard), default()),
token::Variant::AutoScope(t) => Tree::auto_scope(token.with_variant(t)),
token::Variant::SuspendedDefaultArguments(t) => Tree::suspended_default_arguments(token.with_variant(t)),
token::Variant::OpenSymbol(s) =>
Tree::group(Some(token.with_variant(s)), default(), default()).with_error("Unmatched delimiter"),
token::Variant::CloseSymbol(s) =>