Skip to content

Commit

Permalink
feat: auto-import for sway (#5174)
Browse files Browse the repository at this point in the history
## Description

Closes #4782

Notes

This implementation relies on the diagnostics that are provided in the
context of a code action request. Conveniently, only the diagnostics
related to the symbol in the request are provided in the context. Since
we also need the name of the symbol to look up, I added a
`DiagnosticData` type to the metadata that is published with
diagnostics. For the errors that indicate a missing symbol, this data is
attached, and then used to look for possible imports when the code
action request handler runs.

- Added `ProgramTypeKeyword` symbol kind to distinguish them from other
keywords
- Added `submodules_recursive` to lexed and parsed modules because LSP
wasn't collecting tokens inside of nested submodules. This
implementation comes from typed module, I just made it generic via the
`HasSubmodules` and `HasModule` traits.
- Added `TyIncludeStatement` to the compiler, and span and mod_name to
`IncludeStatement` to populate it. This was needed to identify where the
end of the last `mod` statement is in the file.
- Added `span` to `UseStatment` and `TyUseStatement` so that we can
replace an entire Group statement with the new one. e.g. `use a::b::{C,
D, E};`
- Added `CallPath` to several of the typed tokens that were missing it.

### Testing

- integration tests for each type of item that can be imported
- unit tests for the location of the generated import in the file

### Limitations

I found several bugs / edge cases where auto-import doesn't work:
- enum variants: #5188 
- std-lib functions: #5191
- std-lib constants: #5192

## Checklist

- [ ] I have linked to any relevant issues.
- [ ] I have commented my code, particularly in hard-to-understand
areas.
- [ ] I have updated the documentation where relevant (API docs, the
reference, and the Sway book).
- [ ] I have added tests that prove my fix is effective or that my
feature works.
- [ ] I have added (or requested a maintainer to add) the necessary
`Breaking*` or `New Feature` labels where relevant.
- [ ] I have done my best to ensure that my PR adheres to [the Fuel Labs
Code Review
Standards](https://github.com/FuelLabs/rfcs/blob/master/text/code-standards/external-contributors.md).
- [ ] I have requested a review from the relevant team or maintainers.
  • Loading branch information
sdankel authored Oct 20, 2023
1 parent b59aae9 commit 4a09a4b
Show file tree
Hide file tree
Showing 66 changed files with 1,834 additions and 314 deletions.
23 changes: 23 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions sway-ast/src/item/item_use.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,16 @@ pub enum UseTree {
spans: Box<[Span]>,
},
}

impl Spanned for UseTree {
fn span(&self) -> Span {
match self {
UseTree::Group { imports } => imports.span(),
UseTree::Name { name } => name.span(),
UseTree::Rename { name, alias, .. } => Span::join(name.span(), alias.span()),
UseTree::Glob { star_token } => star_token.span(),
UseTree::Path { prefix, suffix, .. } => Span::join(prefix.span(), suffix.span()),
UseTree::Error { spans } => Span::join_all(spans.to_vec().clone()),
}
}
}
34 changes: 32 additions & 2 deletions sway-core/src/language/call_path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ pub struct CallPath<T = Ident> {
pub suffix: T,
// If `is_absolute` is true, then this call path is an absolute path from
// the project root namespace. If not, then it is relative to the current namespace.
pub(crate) is_absolute: bool,
pub is_absolute: bool,
}

impl std::convert::From<Ident> for CallPath {
Expand Down Expand Up @@ -258,6 +258,19 @@ impl CallPath {
}
}

/// Removes the first prefix. Does nothing if prefixes are empty.
pub fn lshift(&self) -> CallPath {
if self.prefixes.is_empty() {
self.clone()
} else {
CallPath {
prefixes: self.prefixes[1..self.prefixes.len()].to_vec(),
suffix: self.suffix.clone(),
is_absolute: self.is_absolute,
}
}
}

pub fn as_vec_string(&self) -> Vec<String> {
self.prefixes
.iter()
Expand All @@ -266,7 +279,7 @@ impl CallPath {
.collect::<Vec<_>>()
}

/// Convert a given `CallPath` to an symbol to a full `CallPath` from the root of the project
/// Convert a given [CallPath] to an symbol to a full [CallPath] from the root of the project
/// in which the symbol is declared. For example, given a path `pkga::SOME_CONST` where `pkga`
/// is an _internal_ library of a package named `my_project`, the corresponding call path is
/// `my_project::pkga::SOME_CONST`.
Expand Down Expand Up @@ -353,4 +366,21 @@ impl CallPath {
}
}
}

/// Convert a given [CallPath] into a call path suitable for a `use` statement.
///
/// For example, given a path `pkga::SOME_CONST` where `pkga` is an _internal_ library of a package named
/// `my_project`, the corresponding call path is `pkga::SOME_CONST`.
///
/// Paths to _external_ libraries such `std::lib1::lib2::my_obj` are left unchanged.
pub fn to_import_path(&self, namespace: &Namespace) -> CallPath {
let converted = self.to_fullpath(namespace);

if let Some(first) = converted.prefixes.first() {
if namespace.root().name == Some(first.clone()) {
return converted.lshift();
}
}
converted
}
}
14 changes: 14 additions & 0 deletions sway-core/src/language/lexed/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ use crate::language::ModName;
pub use program::LexedProgram;
use sway_ast::Module;

use super::{HasModule, HasSubmodules};

/// A module and its submodules in the form of a tree.
#[derive(Debug, Clone)]
pub struct LexedModule {
Expand All @@ -20,3 +22,15 @@ pub struct LexedModule {
pub struct LexedSubmodule {
pub module: LexedModule,
}

impl HasModule<LexedModule> for LexedSubmodule {
fn module(&self) -> &LexedModule {
&self.module
}
}

impl HasSubmodules<LexedSubmodule> for LexedModule {
fn submodules(&self) -> &[(ModName, LexedSubmodule)] {
&self.submodules
}
}
69 changes: 69 additions & 0 deletions sway-core/src/language/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,72 @@ use sway_types::Ident;
/// If an alias was given to the `mod`, this will be the alias. If not, this is the submodule's
/// library name.
pub type ModName = Ident;

pub trait HasModule<T>
where
T: HasSubmodules<Self>,
Self: Sized,
{
/// Returns the module of this submodule.
fn module(&self) -> &T;
}

pub trait HasSubmodules<E>
where
E: HasModule<Self>,
Self: Sized,
{
/// Returns the submodules of this module.
fn submodules(&self) -> &[(ModName, E)];

/// An iterator yielding all submodules recursively, depth-first.
fn submodules_recursive(&self) -> SubmodulesRecursive<Self, E> {
SubmodulesRecursive {
_module_type: std::marker::PhantomData,
submods: self.submodules().iter(),
current: None,
}
}
}

type NamedSubmodule<E> = (ModName, E);
type SubmoduleItem<'module, T, E> = (
&'module NamedSubmodule<E>,
Box<SubmodulesRecursive<'module, T, E>>,
);

/// Iterator type for iterating over submodules.
///
/// Used rather than `impl Iterator` to enable recursive submodule iteration.
pub struct SubmodulesRecursive<'module, T, E> {
_module_type: std::marker::PhantomData<T>,
submods: std::slice::Iter<'module, NamedSubmodule<E>>,
current: Option<SubmoduleItem<'module, T, E>>,
}

impl<'module, T, E> Iterator for SubmodulesRecursive<'module, T, E>
where
T: HasSubmodules<E> + 'module,
E: HasModule<T>,
{
type Item = &'module (ModName, E);
fn next(&mut self) -> Option<Self::Item> {
loop {
self.current = match self.current.take() {
None => match self.submods.next() {
None => return None,
Some(submod) => {
Some((submod, Box::new(submod.1.module().submodules_recursive())))
}
},
Some((submod, mut submods)) => match submods.next() {
Some(next) => {
self.current = Some((submod, submods));
return Some(next);
}
None => return Some(submod),
},
}
}
}
}
9 changes: 6 additions & 3 deletions sway-core/src/language/parsed/include_statement.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
use sway_types::span::Span;
use sway_types::{span::Span, Ident};

use crate::language::Visibility;

#[derive(Clone, Debug)]
pub struct IncludeStatement {
// this span may be used for errors in the future, although it is not right now.
pub(crate) _span: Span,
pub(crate) _mod_name_span: Span,
pub span: Span,
pub mod_name: Ident,
pub visibility: Visibility,
}
2 changes: 1 addition & 1 deletion sway-core/src/language/parsed/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ mod use_statement;
pub use code_block::*;
pub use declaration::*;
pub use expression::*;
pub(crate) use include_statement::IncludeStatement;
pub use include_statement::IncludeStatement;
pub use module::{ParseModule, ParseSubmodule};
pub use program::{ParseProgram, TreeType};
pub use return_statement::*;
Expand Down
14 changes: 13 additions & 1 deletion sway-core/src/language/parsed/module.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{
language::{ModName, Visibility},
language::{HasModule, HasSubmodules, ModName, Visibility},
transform,
};

Expand Down Expand Up @@ -33,3 +33,15 @@ pub struct ParseSubmodule {
pub mod_name_span: Span,
pub visibility: Visibility,
}

impl HasModule<ParseModule> for ParseSubmodule {
fn module(&self) -> &ParseModule {
&self.module
}
}

impl HasSubmodules<ParseSubmodule> for ParseModule {
fn submodules(&self) -> &[(ModName, ParseSubmodule)] {
&self.submodules
}
}
1 change: 1 addition & 0 deletions sway-core/src/language/parsed/use_statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub enum ImportType {
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct UseStatement {
pub call_path: Vec<Ident>,
pub span: Span,
pub import_type: ImportType,
// If `is_absolute` is true, then this use statement is an absolute path from
// the project root namespace. If not, then it is relative to the current namespace.
Expand Down
5 changes: 4 additions & 1 deletion sway-core/src/language/ty/declaration/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use std::{
use sha2::{Digest, Sha256};
use sway_error::handler::{ErrorEmitted, Handler};

use crate::semantic_analysis::type_check_context::MonomorphizeHelper;
use crate::{language::CallPath, semantic_analysis::type_check_context::MonomorphizeHelper};

use crate::{
decl_engine::*,
Expand All @@ -31,6 +31,7 @@ pub struct TyFunctionDecl {
pub parameters: Vec<TyFunctionParameter>,
pub implementing_type: Option<TyDecl>,
pub span: Span,
pub call_path: CallPath,
pub attributes: transform::AttributesMap,
pub type_parameters: Vec<TypeParameter>,
pub return_type: TypeArgument,
Expand Down Expand Up @@ -91,6 +92,7 @@ impl HashWithEngines for TyFunctionDecl {
purity,
// these fields are not hashed because they aren't relevant/a
// reliable source of obj v. obj distinction
call_path: _,
span: _,
attributes: _,
implementing_type: _,
Expand Down Expand Up @@ -234,6 +236,7 @@ impl TyFunctionDecl {
},
implementing_type: None,
span,
call_path: CallPath::from(Ident::dummy()),
attributes: Default::default(),
is_contract_call: false,
parameters: Default::default(),
Expand Down
4 changes: 3 additions & 1 deletion sway-core/src/language/ty/declaration/trait.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::{
DeclRefTraitType, ReplaceFunctionImplementingType,
},
engine_threading::*,
language::{parsed, Visibility},
language::{parsed, CallPath, Visibility},
semantic_analysis::{
type_check_context::MonomorphizeHelper, TypeCheckAnalysis, TypeCheckAnalysisContext,
TypeCheckFinalization, TypeCheckFinalizationContext,
Expand All @@ -30,6 +30,7 @@ pub struct TyTraitDecl {
pub supertraits: Vec<parsed::Supertrait>,
pub visibility: Visibility,
pub attributes: transform::AttributesMap,
pub call_path: CallPath,
pub span: Span,
}

Expand Down Expand Up @@ -85,6 +86,7 @@ impl HashWithEngines for TyTraitDecl {
// reliable source of obj v. obj distinction
attributes: _,
span: _,
call_path: _,
} = self;
name.hash(state);
type_parameters.hash(state, engines);
Expand Down
9 changes: 8 additions & 1 deletion sway-core/src/language/ty/declaration/type_alias.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,17 @@ use std::hash::{Hash, Hasher};

use sway_types::{Ident, Named, Span, Spanned};

use crate::{engine_threading::*, language::Visibility, transform, type_system::*};
use crate::{
engine_threading::*,
language::{CallPath, Visibility},
transform,
type_system::*,
};

#[derive(Clone, Debug)]
pub struct TyTypeAliasDecl {
pub name: Ident,
pub call_path: CallPath,
pub attributes: transform::AttributesMap,
pub ty: TypeArgument,
pub visibility: Visibility,
Expand Down Expand Up @@ -36,6 +42,7 @@ impl HashWithEngines for TyTypeAliasDecl {
visibility,
// these fields are not hashed because they aren't relevant/a
// reliable source of obj v. obj distinction
call_path: _,
span: _,
attributes: _,
} = self;
Expand Down
14 changes: 13 additions & 1 deletion sway-core/src/language/ty/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ use sway_types::Span;

use crate::{
decl_engine::{DeclEngine, DeclRef, DeclRefFunction},
language::ty::*,
language::ModName,
language::{ty::*, HasModule, HasSubmodules},
semantic_analysis::namespace,
transform::{self, AllowDeprecatedState},
Engines,
Expand Down Expand Up @@ -110,3 +110,15 @@ impl<'module> Iterator for SubmodulesRecursive<'module> {
}
}
}

impl HasModule<TyModule> for TySubmodule {
fn module(&self) -> &TyModule {
&self.module
}
}

impl HasSubmodules<TySubmodule> for TyModule {
fn submodules(&self) -> &[(ModName, TySubmodule)] {
&self.submodules
}
}
Loading

0 comments on commit 4a09a4b

Please sign in to comment.