Automatically merge import lines from same module.
I slightly altered the way we parse import definitions to ensure we merge imports from the same modules (that aren't aliased) together. This prevents an annoying warning with duplicated import lines and makes it just more convenient overall. As a trade-off, we can no longer interleave import definitions with other definitions. This should be a minor setback only since the formatter was already ensuring that all import definitions would be grouped at the top. --- Note that, I originally attempted to implement this in the formatter instead of the parser. As it felt more appropriate there. However, the formatter operates on (unmutable) borrowed definitions, which makes it annoyingly hard to perform any AST manipulations. The `Document` returns by the format carries a lifetime that prevents the creation of intermediate local values. So instead, slightly tweaking the parser felt like the right thing to do.
This commit is contained in:
@@ -4,7 +4,7 @@ use crate::{
|
||||
};
|
||||
use chumsky::prelude::*;
|
||||
|
||||
pub fn parser() -> impl Parser<Token, ast::UntypedDefinition, Error = ParseError> {
|
||||
pub fn parser() -> impl Parser<Token, ast::UntypedUse, Error = ParseError> {
|
||||
let unqualified_import = choice((
|
||||
select! {Token::Name { name } => name}.then(
|
||||
just(Token::As)
|
||||
@@ -42,30 +42,28 @@ pub fn parser() -> impl Parser<Token, ast::UntypedDefinition, Error = ParseError
|
||||
.then(as_name);
|
||||
|
||||
just(Token::Use).ignore_then(module_path).map_with_span(
|
||||
|((module, unqualified), as_name), span| {
|
||||
ast::UntypedDefinition::Use(ast::Use {
|
||||
module,
|
||||
as_name,
|
||||
unqualified: unqualified.unwrap_or_default(),
|
||||
package: (),
|
||||
location: span,
|
||||
})
|
||||
|((module, unqualified), as_name), span| ast::Use {
|
||||
module,
|
||||
as_name,
|
||||
unqualified: unqualified.unwrap_or_default(),
|
||||
package: (),
|
||||
location: span,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::assert_definition;
|
||||
use crate::assert_import;
|
||||
|
||||
#[test]
|
||||
fn import_basic() {
|
||||
assert_definition!("use aiken/list");
|
||||
assert_import!("use aiken/list");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn import_unqualified() {
|
||||
assert_definition!(
|
||||
assert_import!(
|
||||
r#"
|
||||
use std/address.{Address as A, thing as w}
|
||||
"#
|
||||
@@ -74,6 +72,6 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn import_alias() {
|
||||
assert_definition!("use aiken/list as foo");
|
||||
assert_import!("use aiken/list as foo");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,25 +3,22 @@ use chumsky::prelude::*;
|
||||
pub mod constant;
|
||||
mod data_type;
|
||||
mod function;
|
||||
mod import;
|
||||
pub mod import;
|
||||
mod test;
|
||||
mod type_alias;
|
||||
mod validator;
|
||||
|
||||
use super::{error::ParseError, token::Token};
|
||||
use crate::ast;
|
||||
pub use constant::parser as constant;
|
||||
pub use data_type::parser as data_type;
|
||||
pub use function::parser as function;
|
||||
pub use import::parser as import;
|
||||
pub use test::parser as test;
|
||||
pub use type_alias::parser as type_alias;
|
||||
pub use validator::parser as validator;
|
||||
|
||||
use super::{error::ParseError, token::Token};
|
||||
use crate::ast;
|
||||
|
||||
pub fn parser() -> impl Parser<Token, ast::UntypedDefinition, Error = ParseError> {
|
||||
choice((
|
||||
import(),
|
||||
data_type(),
|
||||
type_alias(),
|
||||
validator(),
|
||||
|
||||
@@ -2,17 +2,15 @@
|
||||
source: crates/aiken-lang/src/parser/definition/import.rs
|
||||
description: "Code:\n\nuse aiken/list as foo"
|
||||
---
|
||||
Use(
|
||||
Use {
|
||||
as_name: Some(
|
||||
"foo",
|
||||
),
|
||||
location: 0..21,
|
||||
module: [
|
||||
"aiken",
|
||||
"list",
|
||||
],
|
||||
package: (),
|
||||
unqualified: [],
|
||||
},
|
||||
)
|
||||
Use {
|
||||
as_name: Some(
|
||||
"foo",
|
||||
),
|
||||
location: 0..21,
|
||||
module: [
|
||||
"aiken",
|
||||
"list",
|
||||
],
|
||||
package: (),
|
||||
unqualified: [],
|
||||
}
|
||||
|
||||
@@ -2,15 +2,13 @@
|
||||
source: crates/aiken-lang/src/parser/definition/import.rs
|
||||
description: "Code:\n\nuse aiken/list"
|
||||
---
|
||||
Use(
|
||||
Use {
|
||||
as_name: None,
|
||||
location: 0..14,
|
||||
module: [
|
||||
"aiken",
|
||||
"list",
|
||||
],
|
||||
package: (),
|
||||
unqualified: [],
|
||||
},
|
||||
)
|
||||
Use {
|
||||
as_name: None,
|
||||
location: 0..14,
|
||||
module: [
|
||||
"aiken",
|
||||
"list",
|
||||
],
|
||||
package: (),
|
||||
unqualified: [],
|
||||
}
|
||||
|
||||
@@ -2,30 +2,28 @@
|
||||
source: crates/aiken-lang/src/parser/definition/import.rs
|
||||
description: "Code:\n\nuse std/address.{Address as A, thing as w}\n"
|
||||
---
|
||||
Use(
|
||||
Use {
|
||||
as_name: None,
|
||||
location: 0..42,
|
||||
module: [
|
||||
"std",
|
||||
"address",
|
||||
],
|
||||
package: (),
|
||||
unqualified: [
|
||||
UnqualifiedImport {
|
||||
location: 17..29,
|
||||
name: "Address",
|
||||
as_name: Some(
|
||||
"A",
|
||||
),
|
||||
},
|
||||
UnqualifiedImport {
|
||||
location: 31..41,
|
||||
name: "thing",
|
||||
as_name: Some(
|
||||
"w",
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
)
|
||||
Use {
|
||||
as_name: None,
|
||||
location: 0..42,
|
||||
module: [
|
||||
"std",
|
||||
"address",
|
||||
],
|
||||
package: (),
|
||||
unqualified: [
|
||||
UnqualifiedImport {
|
||||
location: 17..29,
|
||||
name: "Address",
|
||||
as_name: Some(
|
||||
"A",
|
||||
),
|
||||
},
|
||||
UnqualifiedImport {
|
||||
location: 31..41,
|
||||
name: "thing",
|
||||
as_name: Some(
|
||||
"w",
|
||||
),
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use chumsky::prelude::*;
|
||||
|
||||
use super::{error::ParseError, token::Token};
|
||||
use chumsky::prelude::*;
|
||||
|
||||
pub fn optional_flag(token: Token) -> impl Parser<Token, bool, Error = ParseError> {
|
||||
just(token).ignored().or_not().map(|v| v.is_some())
|
||||
@@ -132,6 +131,27 @@ macro_rules! assert_definition {
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! assert_import {
|
||||
($code:expr) => {
|
||||
use chumsky::Parser;
|
||||
|
||||
let $crate::parser::lexer::LexInfo { tokens, .. } = $crate::parser::lexer::run(indoc::indoc! { $code }).unwrap();
|
||||
|
||||
let stream = chumsky::Stream::from_iter($crate::ast::Span::create(tokens.len(), 1), tokens.into_iter());
|
||||
|
||||
let result = $crate::parser::import().parse(stream).unwrap();
|
||||
|
||||
insta::with_settings!({
|
||||
description => concat!("Code:\n\n", indoc::indoc! { $code }),
|
||||
prepend_module_to_snapshot => false,
|
||||
omit_expression => true
|
||||
}, {
|
||||
insta::assert_debug_snapshot!(result);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! assert_format {
|
||||
($code:expr) => {
|
||||
|
||||
Reference in New Issue
Block a user