Skip to content

Feature/error pythonic import #4401

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from
4 changes: 4 additions & 0 deletions changelog/v1.9.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@
size.
([Surya Rose](https://github.com/GearsDatapacks))

- Gleam will now issue a help message when attempting to import using Python's
import syntax.
([Zij-IT](https://github.com/zij-it))

### Build tool

- The build tool now supports Git dependencies. For example:
Expand Down
28 changes: 26 additions & 2 deletions compiler-core/src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2686,8 +2686,32 @@ where
let mut unqualified_values = vec![];
let mut unqualified_types = vec![];

if self.maybe_one(&Token::Dot).is_some() {
let _ = self.expect_one(&Token::LeftBrace)?;
if let Some((dot_start, dot_end)) = self.maybe_one(&Token::Dot) {
let _ = self.expect_one(&Token::LeftBrace).map_err(|e| {
// If the module does contain a '/', then it's unlikely that the user
// intended for the import to be pythonic, so skip this.
if module.contains('/') {
return e;
}

// Catch `import gleam.io` and provide a more helpful error...
let ParseErrorType::UnexpectedToken {
token: Token::Name { name } | Token::UpName { name },
..
} = &e.error
else {
return e;
};

ParseError {
error: ParseErrorType::PythonicImport {
module: module.as_str().into(),
item: name.clone(),
},
location: SrcSpan::new(dot_start, dot_end),
}
})?;

let parsed = self.parse_unqualified_imports()?;
unqualified_types = parsed.types;
unqualified_values = parsed.values;
Expand Down
15 changes: 15 additions & 0 deletions compiler-core/src/parse/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,16 @@ utf16_codepoint, utf32_codepoint, signed, unsigned, big, little, native, size, u
"This attribute cannot be used on a variant.",
vec!["Hint: Did you mean `@deprecated`?".into()],
),
ParseErrorType::PythonicImport { module, item } => (
"I was expecting either `/` or `.{` here.",
vec![
"This syntax for an import is incorrect. Perhaps you meant:".into(),
format!(" - `import {module}/{item}` to import the `{item}` \
module from the `{module}` namespace"),
format!(" - `import {module}.{{{item}}}` to import the \
`{item}` value from the `{module}` module"),
]
)
}
}
}
Expand Down Expand Up @@ -411,6 +421,11 @@ pub enum ParseErrorType {
TypeConstructorNoArguments, // let a : Int()
TypeDefinitionNoArguments, // pub type Wibble() { ... }
UnknownAttributeRecordVariant, // an attribute was used that is not know for a custom type variant
// a Python-like import was written, such as `import gleam.io`, instead of `import gleam/io`
PythonicImport {
module: EcoString,
item: EcoString,
},
}

impl LexicalError {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
source: compiler-core/src/parse/tests.rs
expression: import one/two.three
---
----- SOURCE CODE
import one/two.three

----- ERROR
error: Syntax error
┌─ /src/parse/error.gleam:1:16
1 │ import one/two.three
│ ^^^^^ I was not expecting this

Found a name, expected one of:
- `{`
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
source: compiler-core/src/parse/tests.rs
expression: import gleam.io
---
----- SOURCE CODE
import gleam.io

----- ERROR
error: Syntax error
┌─ /src/parse/error.gleam:1:13
1 │ import gleam.io
│ ^ I was expecting either `/` or `.{` here.

This syntax for an import is incorrect. Perhaps you meant:
- `import gleam/io` to import the `io` module from the `gleam` namespace
- `import gleam.{io}` to import the `io` module from the `gleam` module
10 changes: 10 additions & 0 deletions compiler-core/src/parse/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1766,3 +1766,13 @@ fn nested_tuple_access_after_function() {
fn case_expression_without_body() {
assert_parse!("case a");
}

#[test]
fn special_error_for_pythonic_import() {
assert_module_error!("import gleam.io");
}

#[test]
fn doesnt_issue_special_error_for_pythonic_import_if_slash() {
assert_module_error!("import one/two.three");
}