Skip to content
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

Provide useful error for "Cannot find trait Hash in this scope" #5293

Merged
merged 10 commits into from
Nov 22, 2023
14 changes: 14 additions & 0 deletions sway-core/src/decl_engine/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,20 @@ impl DeclEngine {
self.get(index)
}

/// Returns all the [ty::TyTraitDecl]s whose name is the same as `trait_name`.
///
/// The method does a linear search over all the declared traits and is meant
/// to be used only for diagnostic purposes.
pub fn get_traits_by_name(&self, trait_name: &Ident) -> Vec<ty::TyTraitDecl> {
self.trait_slab.with_slice(|elems| {
elems
.iter()
.filter(|trait_decl| trait_decl.name == *trait_name)
.cloned()
.collect()
})
}

/// Friendly helper method for calling the `get` method from the
/// implementation of [DeclEngineGet] for [DeclEngine]
///
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -800,6 +800,7 @@ impl ReplaceDecls for TyExpressionVariant {
handler,
ctx.by_ref(),
&method.type_parameters,
method.name.as_str(),
&method.name.span(),
)?;
method.replace_decls(&inner_decl_mapping, handler, ctx)?;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1425,7 +1425,13 @@ fn handle_supertraits(

match ctx
.namespace
.resolve_call_path(handler, engines, &supertrait.name, ctx.self_type())
// Use the default Handler to avoid emitting the redundant SymbolNotFound error.
.resolve_call_path(
&Handler::default(),
engines,
&supertrait.name,
ctx.self_type(),
)
.ok()
{
Some(ty::TyDecl::TraitDecl(ty::TraitDecl { decl_id, .. })) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,13 @@ pub(crate) fn insert_supertraits_into_namespace(

let decl = ctx
.namespace
.resolve_call_path(handler, engines, &supertrait.name, ctx.self_type())
// Use the default Handler to avoid emitting the redundant SymbolNotFound error.
.resolve_call_path(
&Handler::default(),
engines,
&supertrait.name,
ctx.self_type(),
)
.ok();

match (decl.clone(), supertraits_of) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ pub(crate) fn instantiate_function_application(
handler,
ctx.by_ref(),
&function_decl.type_parameters,
function_decl.name.as_str(),
&call_path_binding.span(),
)?;
function_decl.replace_decls(&decl_mapping, handler, &mut ctx)?;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,7 @@ pub(crate) fn monomorphize_method_application(
handler,
ctx.by_ref(),
&method.type_parameters,
method.name.as_str(),
&call_path.span(),
)?;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,8 @@ impl TraitConstraint {

match ctx
.namespace
.resolve_call_path(handler, engines, trait_name, ctx.self_type())
// Use the default Handler to avoid emitting the redundant SymbolNotFound error.
.resolve_call_path(&Handler::default(), engines, trait_name, ctx.self_type())
.ok()
{
Some(ty::TyDecl::TraitDecl(ty::TraitDecl { decl_id, .. })) => {
Expand Down
48 changes: 42 additions & 6 deletions sway-core/src/type_system/ast_elements/type_parameter.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use crate::{
decl_engine::*,
engine_threading::*,
language::{ty, CallPath},
language::{
ty::{self},
CallPath,
},
namespace::TryInsertingTraitImplOnFailure,
semantic_analysis::*,
type_system::priv_prelude::*,
Expand Down Expand Up @@ -427,10 +430,12 @@ impl TypeParameter {
}

/// Creates a [DeclMapping] from a list of [TypeParameter]s.
/// `function_name` and `access_span` are used only for error reporting.
pub(crate) fn gather_decl_mapping_from_trait_constraints(
handler: &Handler,
ctx: TypeCheckContext,
type_parameters: &[TypeParameter],
function_name: &str,
access_span: &Span,
) -> Result<DeclMapping, ErrorEmitted> {
let mut interface_item_refs: InterfaceItemMap = BTreeMap::new();
Expand Down Expand Up @@ -475,6 +480,8 @@ impl TypeParameter {
*type_id,
trait_name,
trait_type_arguments,
function_name,
access_span.clone(),
) {
Ok(res) => res,
Err(_) => continue,
Expand All @@ -501,6 +508,8 @@ fn handle_trait(
type_id: TypeId,
trait_name: &CallPath,
type_arguments: &[TypeArgument],
function_name: &str,
access_span: Span,
) -> Result<(InterfaceItemMap, ItemMap, ItemMap), ErrorEmitted> {
let engines = ctx.engines;
let decl_engine = engines.de();
Expand All @@ -512,7 +521,8 @@ fn handle_trait(
handler.scope(|handler| {
match ctx
.namespace
.resolve_call_path(handler, engines, trait_name, ctx.self_type())
// Use the default Handler to avoid emitting the redundant SymbolNotFound error.
.resolve_call_path(&Handler::default(), engines, trait_name, ctx.self_type())
.ok()
{
Some(ty::TyDecl::TraitDecl(ty::TraitDecl { decl_id, .. })) => {
Expand All @@ -534,7 +544,15 @@ fn handle_trait(
supertrait_interface_item_refs,
supertrait_item_refs,
supertrait_impld_item_refs,
) = match handle_trait(handler, ctx, type_id, &supertrait.name, &[]) {
) = match handle_trait(
handler,
ctx,
type_id,
&supertrait.name,
&[],
function_name,
access_span.clone(),
) {
Ok(res) => res,
Err(_) => continue,
};
Expand All @@ -544,9 +562,27 @@ fn handle_trait(
}
}
_ => {
handler.emit_err(CompileError::TraitNotFound {
name: trait_name.to_string(),
span: trait_name.span(),
let trait_candidates = decl_engine
.get_traits_by_name(&trait_name.suffix)
.iter()
.map(|trait_decl| {
// In the case of an internal library, always add :: to the candidate call path.
let import_path = trait_decl.call_path.to_import_path(ctx.namespace);
if import_path == trait_decl.call_path {
// If external library.
import_path.to_string()
} else {
format!("::{import_path}")
}
})
.collect();

handler.emit_err(CompileError::TraitNotImportedAtFunctionApplication {
trait_name: trait_name.suffix.to_string(),
function_name: function_name.to_string(),
function_call_site_span: access_span.clone(),
trait_constraint_span: trait_name.suffix.span(),
trait_candidates,
});
}
}
Expand Down
108 changes: 107 additions & 1 deletion sway-error/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::type_error::TypeError;

use core::fmt;
use sway_types::constants::STORAGE_PURITY_ATTRIBUTE_NAME;
use sway_types::{BaseIdent, Ident, SourceEngine, Span, Spanned};
use sway_types::{BaseIdent, Ident, SourceEngine, SourceId, Span, Spanned};
use thiserror::Error;

#[derive(Error, Debug, Clone, PartialEq, Eq, Hash)]
Expand Down Expand Up @@ -386,6 +386,14 @@ pub enum CompileError {
UnitVariantWithParenthesesEnumInstantiator { span: Span, ty: String },
#[error("Cannot find trait \"{name}\" in this scope.")]
TraitNotFound { name: String, span: Span },
#[error("Trait \"{trait_name}\" is not imported when calling \"{function_name}\".\nThe import is needed because \"{function_name}\" uses \"{trait_name}\" in one of its trait constraints.")]
TraitNotImportedAtFunctionApplication {
trait_name: String,
function_name: String,
function_call_site_span: Span,
trait_constraint_span: Span,
trait_candidates: Vec<String>,
},
#[error("This expression is not valid on the left hand side of a reassignment.")]
InvalidExpressionOnLhs { span: Span },
#[error("This code cannot be evaluated to a constant")]
Expand Down Expand Up @@ -825,6 +833,10 @@ impl Spanned for CompileError {
UnnecessaryEnumInstantiator { span, .. } => span.clone(),
UnitVariantWithParenthesesEnumInstantiator { span, .. } => span.clone(),
TraitNotFound { span, .. } => span.clone(),
TraitNotImportedAtFunctionApplication {
function_call_site_span,
..
} => function_call_site_span.clone(),
InvalidExpressionOnLhs { span, .. } => span.clone(),
TooManyArgumentsForFunction { span, .. } => span.clone(),
TooFewArgumentsForFunction { span, .. } => span.clone(),
Expand Down Expand Up @@ -1114,6 +1126,90 @@ impl ToDiagnostic for CompileError {
format!("Consider removing the variable \"{variable}\" altogether, or adding it to all alternatives."),
],
},
TraitNotImportedAtFunctionApplication { trait_name, function_name, function_call_site_span, trait_constraint_span, trait_candidates }=> Diagnostic {
reason: Some(Reason::new(code(1), "Trait is not imported".to_string())),
issue: Issue::error(
source_engine,
function_call_site_span.clone(),
format!(
"Trait \"{trait_name}\" is not imported {}when calling \"{function_name}\".",
get_file_name(source_engine, function_call_site_span.source_id())
.map_or("".to_string(), |file_name| format!("into \"{file_name}\" "))
)
),
hints: {
let mut hints = vec![
Hint::help(
source_engine,
function_call_site_span.clone(),
format!("This import is needed because \"{function_name}\" requires \"{trait_name}\" in one of its trait constraints.")
),
Hint::info(
source_engine,
trait_constraint_span.clone(),
format!("In the definition of \"{function_name}\", \"{trait_name}\" is used in this trait constraint.")
),
];

match trait_candidates.len() {
// If no candidates are found, that means that an alias was used in the trait constraint definition.
// The way how constraint checking works now, the trait will not be found when we try to check if
// the trait constraints are satisfied for type, and we will never end up in this case here.
// So we will simply ignore it.
0 => (),
// The most common case. Exactly one known trait with the given name.
1 => hints.push(Hint::help(
source_engine,
function_call_site_span.clone(),
format!(
"Import the \"{trait_name}\" trait {}by using: `use {};`.",
get_file_name(source_engine, function_call_site_span.source_id())
.map_or("".to_string(), |file_name| format!("into \"{file_name}\" ")),
trait_candidates[0]
)
)),
// Unlikely (for now) case of having several traits with the same name.
_ => hints.push(Hint::help(
source_engine,
function_call_site_span.clone(),
format!(
"To import the proper \"{trait_name}\" {}follow the detailed instructions given below.",
get_file_name(source_engine, function_call_site_span.source_id())
.map_or("".to_string(), |file_name| format!("into \"{file_name}\" "))
)
)),
}

hints
},
help: {
let mut help = vec![];

if trait_candidates.len() > 1 {
help.push(format!("There are these {} traits with the name \"{trait_name}\" available in the modules:", trait_candidates.len()));
for trait_candidate in trait_candidates.iter() {
help.push(format!(" - {trait_candidate}"));
}
help.push("To import the proper one follow these steps:".to_string());
help.push(format!(
" 1. Look at the definition of the \"{function_name}\"{}.",
get_file_name(source_engine, trait_constraint_span.source_id())
.map_or("".to_string(), |file_name| format!(" in the \"{file_name}\""))
));
help.push(format!(
" 2. Detect which exact \"{trait_name}\" is used in the trait constraint in the \"{function_name}\"."
));
help.push(format!(
" 3. Import that \"{trait_name}\"{}.",
get_file_name(source_engine, function_call_site_span.source_id())
.map_or("".to_string(), |file_name| format!(" into \"{file_name}\""))
));
help.push(format!(" E.g., assuming it is the first one on the list, use: `use {};`", trait_candidates[0]));
}

help
},
},
_ => Diagnostic {
// TODO: Temporary we use self here to achieve backward compatibility.
// In general, self must not be used and will not be used once we
Expand Down Expand Up @@ -1149,3 +1245,13 @@ pub enum TypeNotAllowedReason {
#[error("`str` or a type containing `str` on `const` is not allowed.")]
StringSliceInConst,
}

/// Returns the file name (with extension) for the provided `source_id`,
/// or `None` if the `source_id` is `None` or the file name cannot be
/// obtained.
fn get_file_name(source_engine: &SourceEngine, source_id: Option<&SourceId>) -> Option<String> {
match source_id {
Some(source_id) => source_engine.get_file_name(source_id),
None => None,
}
}
3 changes: 3 additions & 0 deletions sway-lsp/src/capabilities/diagnostic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@ impl TryFrom<CompileError> for DiagnosticData {
CompileError::SymbolNotFound { name, .. } => Ok(DiagnosticData {
unknown_symbol_name: Some(name.to_string()),
}),
CompileError::TraitNotFound { name, .. } => Ok(DiagnosticData {
unknown_symbol_name: Some(name),
}),
CompileError::UnknownVariable { var_name, .. } => Ok(DiagnosticData {
unknown_symbol_name: Some(var_name.to_string()),
}),
Expand Down
9 changes: 9 additions & 0 deletions sway-types/src/source_engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,13 @@ impl SourceEngine {
pub fn get_module_id(&self, path: &PathBuf) -> Option<ModuleId> {
self.path_to_module_map.read().unwrap().get(path).cloned()
}

/// This function provides the file name (with extension) corresponding to a specified source ID.
pub fn get_file_name(&self, source_id: &SourceId) -> Option<String> {
self.get_path(source_id)
.as_path()
.file_name()
.map(|file_name| file_name.to_string_lossy())
.map(|file_name| file_name.to_string())
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
category = "fail"

# check: $()Could not find symbol "C" in this scope.
# check: $()Cannot find trait "C" in this scope.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[[package]]
name = "core"
source = "path+from-root-AC247AEA3D39B916"

[[package]]
name = "std"
source = "git+https://github.com/fuellabs/sway?tag=v0.47.0#34265301c6037d51444899a99df1cfc563df6016"
dependencies = ["core"]

[[package]]
name = "trait_cannot_find_in_scope_issue"
source = "member"
dependencies = ["std"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[project]
name = "trait_cannot_find_in_scope_issue"
authors = ["Fuel Labs <contact@fuel.sh>"]
entry = "main.sw"
license = "Apache-2.0"
implicit-std = true
Loading
Loading