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 9, 2025
1 parent 845948a commit 7cb6fd4
Show file tree
Hide file tree
Showing 3 changed files with 169 additions and 17 deletions.
44 changes: 33 additions & 11 deletions crates/cairo-lang-semantic/src/diagnostic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -795,21 +795,42 @@ 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() {
format!(
"Method `{}` not found on type `{}`. Did you import the correct trait and \
impl?",
method_name,
ty.format(db)
)
} else {
format!(
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)
)
);
}
match relevant_traits {
Some(relevant_traits) 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 `{}`. Consider importing one of the \
following traits: {}.",
method_name,
ty.format(db),
suggestions
)
}
_ => format!(
"Method `{}` not found on type `{}`. Did you import the correct trait and \
impl?",
method_name,
ty.format(db)
),
}
}
SemanticDiagnosticKind::TailExpressionNotAllowedInLoop => {
Expand Down Expand Up @@ -1212,6 +1233,7 @@ pub enum SemanticDiagnosticKind {
ty: semantic::TypeId,
method_name: SmolStr,
inference_errors: TraitInferenceErrors,
relevant_traits: Option<Vec<String>>,
},
NoSuchStructMember {
struct_id: StructId,
Expand Down
49 changes: 46 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,46 @@ error: Type not found.
--> lib.cairo:1:23
#[implicit_precedence(MissingBuiltin1, MissingBuiltin2)]
^^^^^^^^^^^^^^^

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

//! > Test suggesting import for missing method.

//! > test_runner_name
test_expr_diagnostics(expect_diagnostics: true)

//! > expr_code
{
// TODO: Ensure the diagnostic matches the expected output- add the tests' crate to the db.
let x: a_struct = A {a: 0};
x.foo()
}

//! > module_code
struct A {
a: felt252,
}
mod some_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: Type not found.
--> lib.cairo:18:12
let x: a_struct = A {a: 0};
^^^^^^^^

error[E0002]: Method `foo` not found on type `<missing>`. Did you import the correct trait and impl?
--> lib.cairo:19:7
x.foo()
^^^
93 changes: 90 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 All @@ -71,6 +72,7 @@ use crate::items::modifiers::compute_mutability;
use crate::items::us::get_use_path_segments;
use crate::items::visibility;
use crate::literals::try_extract_minus_literal;
use crate::lsp_helpers::TypeFilter;
use crate::resolve::{
EnrichedMembers, EnrichedTypeMemberAccess, ResolvedConcreteItem, ResolvedGenericItem, Resolver,
};
Expand Down Expand Up @@ -2778,6 +2780,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 =
find_relevant_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 +2795,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 +3814,77 @@ 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 find_relevant_traits(
db: &dyn SemanticGroup,
ty: semantic::TypeId,
method_name: &SmolStr,
ctx: &mut ComputationContext<'_>,
stable_ptr: SyntaxStablePtrId,
) -> Option<Vec<String>> {
let module_file_id = ctx.resolver.module_file_id;
let visible_traits = db.visible_traits_from_module(module_file_id)?;
let relevant_methods = find_methods_for_type(db, &mut ctx.resolver, ty, stable_ptr)?;
let mut suggestions = vec![];

// Check if the relevant methods belong to visible traits.
for method in relevant_methods {
if method.name(db.upcast()) == *method_name {
if let Some(trait_path) = visible_traits.get(&method.trait_id(db.upcast())).cloned() {
suggestions.push(trait_path);
}
}
}
Some(suggestions)
}

/// Finds all methods that can be called on a type.
pub fn find_methods_for_type(
db: &dyn SemanticGroup,
resolver: &mut Resolver<'_>,
ty: crate::TypeId,
stable_ptr: SyntaxStablePtrId,
) -> Option<Vec<TraitFunctionId>> {
let type_filter = match ty.head(db) {
Some(head) => TypeFilter::TypeHead(head),
None => TypeFilter::NoFilter,
};

let mut relevant_methods = Vec::new();
// Find methods on type.
// TODO(spapini): Look only in current crate dependencies.
for crate_id in db.crates() {
let methods = db.methods_in_crate(crate_id, type_filter.clone());
for trait_function in methods.iter().copied() {
let clone_data =
&mut resolver.inference().clone_with_inference_id(db, InferenceId::NoContext);
let mut inference = clone_data.inference(db);
let lookup_context = resolver.impl_lookup_context();
// Check if trait function signature's first param can fit our expr type.
let (concrete_trait_id, _) = inference.infer_concrete_trait_by_self(
trait_function,
ty,
&lookup_context,
Some(stable_ptr),
|_| {},
)?;

inference.solve().ok();
if !matches!(
inference.trait_solution_set(
concrete_trait_id,
ImplVarTraitItemMappings::default(),
lookup_context
),
Ok(SolutionSet::Unique(_) | SolutionSet::Ambiguous(_))
) {
continue;
}
relevant_methods.push(trait_function);
}
}
Some(relevant_methods)
}

0 comments on commit 7cb6fd4

Please sign in to comment.