Skip to content

Commit

Permalink
matching un-imported traits
Browse files Browse the repository at this point in the history
  • Loading branch information
dean-starkware committed Jan 11, 2025
1 parent 845948a commit cdf03a5
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 17 deletions.
37 changes: 29 additions & 8 deletions crates/cairo-lang-semantic/src/diagnostic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -795,20 +795,40 @@ impl DiagnosticEntry for SemanticDiagnostic {
"`#[inline(always)]` is not allowed for functions with impl generic parameters."
.into()
}
SemanticDiagnosticKind::CannotCallMethod { ty, method_name, inference_errors } => {
if inference_errors.is_empty() {
SemanticDiagnosticKind::CannotCallMethod {
ty,
method_name,
inference_errors,
relevant_traits,
} => {
if !inference_errors.is_empty() {
return format!(
"Method `{}` could not be called on type `{}`.\n{}",
method_name,
ty.format(db),
inference_errors.format(db)
);
}
if !relevant_traits.is_empty() {
let suggestions = relevant_traits
.iter()
.map(|trait_path| format!("`{trait_path}`"))
.collect::<Vec<_>>()
.join(", ");

format!(
"Method `{}` not found on type `{}`. Did you import the correct trait and \
impl?",
"Method `{}` not found on type `{}`. Consider importing one of the \
following traits: {}.",
method_name,
ty.format(db)
ty.format(db),
suggestions
)
} else {
format!(
"Method `{}` could not be called on type `{}`.\n{}",
"Method `{}` not found on type `{}`. Did you import the correct trait and \
impl?",
method_name,
ty.format(db),
inference_errors.format(db)
ty.format(db)
)
}
}
Expand Down Expand Up @@ -1212,6 +1232,7 @@ pub enum SemanticDiagnosticKind {
ty: semantic::TypeId,
method_name: SmolStr,
inference_errors: TraitInferenceErrors,
relevant_traits: Vec<String>,
},
NoSuchStructMember {
struct_id: StructId,
Expand Down
43 changes: 40 additions & 3 deletions crates/cairo-lang-semantic/src/diagnostic_test_data/not_found
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! > Test PathNotFound.

//! > test_runner_name
test_expr_diagnostics
test_expr_diagnostics(expect_diagnostics: true)

//! > expr_code
{
Expand Down Expand Up @@ -30,7 +30,7 @@ error: Function not found.
//! > Test trying to access a function from a module whose file is missing.

//! > test_runner_name
test_expr_diagnostics
test_expr_diagnostics(expect_diagnostics: true)

//! > expr_code
module_does_not_exist::bar()
Expand All @@ -51,7 +51,7 @@ mod module_does_not_exist;
//! > Test missing implicit in implicit_precedence

//! > test_runner_name
test_expr_diagnostics
test_expr_diagnostics(expect_diagnostics: true)

//! > expr_code
{}
Expand All @@ -67,3 +67,40 @@ error: Type not found.
--> lib.cairo:1:23
#[implicit_precedence(MissingBuiltin1, MissingBuiltin2)]
^^^^^^^^^^^^^^^

//! > ==========================================================================

//! > Test matching imports for missing methods.

//! > test_runner_name
test_expr_diagnostics(expect_diagnostics: true)

//! > expr_code
{
let struct_a = A {a: 0};
struct_a.foo()
}

//! > module_code
struct A {
a: felt252,
}
mod module {
use super::A;
pub trait Trt1 {
fn foo(self: A) -> felt252;
}
impl Imp1 of Trt1 {
fn foo(self: A) -> felt252 {
0
}
}
}

//! > function_body

//! > expected_diagnostics
error[E0002]: Method `foo` not found on type `test::A`. Consider importing one of the following traits: `module::Trt1`.
--> lib.cairo:18:14
struct_a.foo()
^^^
62 changes: 59 additions & 3 deletions crates/cairo-lang-semantic/src/expr/compute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,12 @@ use cairo_lang_utils::ordered_hash_map::{Entry, OrderedHashMap};
use cairo_lang_utils::ordered_hash_set::OrderedHashSet;
use cairo_lang_utils::unordered_hash_map::UnorderedHashMap;
use cairo_lang_utils::unordered_hash_set::UnorderedHashSet;
use cairo_lang_utils::{LookupIntern, OptionHelper, extract_matches, try_extract_matches};
use cairo_lang_utils::{Intern, LookupIntern, OptionHelper, extract_matches, try_extract_matches};
use itertools::{Itertools, chain, zip_eq};
use num_bigint::BigInt;
use num_traits::ToPrimitive;
use salsa::InternKey;
use smol_str::SmolStr;
use utils::Intern;

use super::inference::canonic::ResultNoErrEx;
use super::inference::conform::InferenceConform;
Expand All @@ -62,6 +61,8 @@ use crate::diagnostic::{
ElementKind, MultiArmExprKind, NotFoundItemType, SemanticDiagnostics,
SemanticDiagnosticsBuilder, TraitInferenceErrors, UnsupportedOutsideOfFunctionFeatureName,
};
use crate::expr::inference::solver::SolutionSet;
use crate::expr::inference::{ImplVarTraitItemMappings, InferenceId};
use crate::items::constant::{ConstValue, resolve_const_expr_and_evaluate, validate_const_expr};
use crate::items::enm::SemanticEnumEx;
use crate::items::feature_kind::extract_item_feature_config;
Expand Down Expand Up @@ -2778,6 +2779,12 @@ fn method_call_expr(
}
}

// Extracting the possible traits that should be imported, in order to use the method.
let method_name =
expr.path(ctx.db.upcast()).node.clone().get_text_without_trivia(ctx.db.upcast());
let ty = ctx.reduce_ty(lexpr.ty());
let relevant_traits =
match_method_to_traits(ctx.db, ty, &method_name.into(), ctx, lexpr.stable_ptr().untyped());
let (function_id, actual_trait_id, fixed_lexpr, mutability) =
compute_method_function_call_data(
ctx,
Expand All @@ -2787,7 +2794,12 @@ fn method_call_expr(
path.stable_ptr().untyped(),
generic_args_syntax,
|ty, method_name, inference_errors| {
Some(CannotCallMethod { ty, method_name, inference_errors })
Some(CannotCallMethod {
ty,
method_name,
inference_errors,
relevant_traits: relevant_traits.clone(),
})
},
|_, trait_function_id0, trait_function_id1| {
Some(AmbiguousTrait { trait_function_id0, trait_function_id1 })
Expand Down Expand Up @@ -3801,3 +3813,47 @@ fn function_parameter_types(
let param_types = signature.params.into_iter().map(|param| param.ty);
Ok(param_types)
}

/// Finds traits which contain a method matching the given name and type.
/// This function checks for visible traits in the specified module file and filters
/// methods based on their association with the given type and method name.
fn match_method_to_traits(
db: &dyn SemanticGroup,
ty: semantic::TypeId,
method_name: &SmolStr,
ctx: &mut ComputationContext<'_>,
stable_ptr: SyntaxStablePtrId,
) -> Vec<String> {
let visible_traits = db
.visible_traits_from_module(ctx.resolver.module_file_id)
.unwrap_or_else(|| Arc::new(OrderedHashMap::default()));

visible_traits
.iter()
.filter_map(|(trait_id, path)| {
let clone_data =
&mut ctx.resolver.inference().clone_with_inference_id(db, InferenceId::NoContext);
let mut inference = clone_data.inference(db);
let lookup_context = ctx.resolver.impl_lookup_context();
let trait_function =
db.trait_function_by_name(*trait_id, method_name.clone()).ok()??;
let (concrete_trait_id, _) = inference.infer_concrete_trait_by_self(
trait_function,
ty,
&lookup_context,
Some(stable_ptr),
|_| {},
)?;
inference.solve().ok();

match inference.trait_solution_set(
concrete_trait_id,
ImplVarTraitItemMappings::default(),
lookup_context,
) {
Ok(SolutionSet::Unique(_) | SolutionSet::Ambiguous(_)) => Some(path.clone()),
_ => None,
}
})
.collect()
}
19 changes: 16 additions & 3 deletions crates/cairo-lang-semantic/src/lsp_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@ use cairo_lang_defs::ids::{
FileIndex, LanguageElementId, ModuleFileId, ModuleId, NamedLanguageElementId, TraitFunctionId,
TraitId,
};
use cairo_lang_filesystem::ids::CrateId;
use cairo_lang_filesystem::db::CORELIB_CRATE_NAME;
use cairo_lang_filesystem::ids::{CrateId, CrateLongId};
use cairo_lang_utils::Intern;
use cairo_lang_utils::ordered_hash_map::{Entry, OrderedHashMap};
use cairo_lang_utils::unordered_hash_set::UnorderedHashSet;
use itertools::chain;
use smol_str::SmolStr;

use crate::corelib::{core_submodule, get_submodule};
use crate::corelib::{self, core_submodule, get_submodule};
use crate::db::SemanticGroup;
use crate::expr::inference::InferenceId;
use crate::items::us::SemanticUseEx;
Expand Down Expand Up @@ -244,7 +247,17 @@ pub fn visible_traits_from_module(
);
module_visible_traits
.extend_from_slice(&db.visible_traits_in_module(module_id, module_file_id, true)[..]);
for crate_id in db.crates() {
let settings = db.crate_config(current_crate_id).map(|c| c.settings).unwrap_or_default();
for crate_id in chain!(
(!settings.dependencies.contains_key(CORELIB_CRATE_NAME)).then(|| corelib::core_crate(db)),
settings.dependencies.iter().map(|(name, setting)| {
CrateLongId::Real {
name: name.clone().into(),
discriminator: setting.discriminator.clone(),
}
.intern(db)
})
) {
if crate_id == current_crate_id {
continue;
}
Expand Down

0 comments on commit cdf03a5

Please sign in to comment.