From 0f453eb1bdb3e469b62e671fab7711e9e2b92872 Mon Sep 17 00:00:00 2001 From: zongz <68977949+zong-zhe@users.noreply.github.com> Date: Mon, 18 Sep 2023 17:23:33 +0800 Subject: [PATCH] feat: add fuzz match for error 'attribute not found' (#715) * feat: add fuzz match for error 'attribute not found' * fix: add 'suggestions' to supports fuzz match * fix: rm useless imports * fix: make fmt --- kclvm/Cargo.lock | 10 +++++ kclvm/cmd/src/test_data/fuzz_match/main.k | 6 +++ .../src/test_data/fuzz_match/main_unmatched.k | 6 +++ kclvm/cmd/src/tests.rs | 37 +++++++++++++++++++ kclvm/sema/Cargo.toml | 1 + kclvm/sema/src/resolver/attr.rs | 28 +++++++++++--- 6 files changed, 82 insertions(+), 6 deletions(-) create mode 100644 kclvm/cmd/src/test_data/fuzz_match/main.k create mode 100644 kclvm/cmd/src/test_data/fuzz_match/main_unmatched.k diff --git a/kclvm/Cargo.lock b/kclvm/Cargo.lock index 396095451..6089be869 100644 --- a/kclvm/Cargo.lock +++ b/kclvm/Cargo.lock @@ -1769,6 +1769,7 @@ dependencies = [ "petgraph", "phf", "regex", + "suggestions", "unicode_names2", ] @@ -3304,6 +3305,15 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "suggestions" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5441c382482e49aaac2c3ea9cbcd24290531246e879ee94af5dfc4b144f11e80" +dependencies = [ + "strsim", +] + [[package]] name = "syn" version = "1.0.109" diff --git a/kclvm/cmd/src/test_data/fuzz_match/main.k b/kclvm/cmd/src/test_data/fuzz_match/main.k new file mode 100644 index 000000000..7cf458d62 --- /dev/null +++ b/kclvm/cmd/src/test_data/fuzz_match/main.k @@ -0,0 +1,6 @@ +schema Person: + aa?: int + aaa?: int + +p = Person {} +a = p.a # Error, attribute 'a' not found in schema `Person`, did you mean `aa`? \ No newline at end of file diff --git a/kclvm/cmd/src/test_data/fuzz_match/main_unmatched.k b/kclvm/cmd/src/test_data/fuzz_match/main_unmatched.k new file mode 100644 index 000000000..105956175 --- /dev/null +++ b/kclvm/cmd/src/test_data/fuzz_match/main_unmatched.k @@ -0,0 +1,6 @@ +schema Person: + en?: int + sd?: int + +p = Person {} +a = p.a # Error, attribute 'a' not found in schema `Person` \ No newline at end of file diff --git a/kclvm/cmd/src/tests.rs b/kclvm/cmd/src/tests.rs index 5d4ba5423..445889ef9 100644 --- a/kclvm/cmd/src/tests.rs +++ b/kclvm/cmd/src/tests.rs @@ -559,3 +559,40 @@ fn test_plugin_not_found() { Err(msg) => assert!(msg.contains("the plugin package `kcl_plugin.not_exist` is not found, please confirm if plugin mode is enabled")), } } + +#[test] +fn test_error_message_fuzz_matched() { + let test_case_path = PathBuf::from("./src/test_data/fuzz_match/main.k"); + let matches = app().arg_required_else_help(true).get_matches_from(&[ + ROOT_CMD, + "run", + &test_case_path.canonicalize().unwrap().display().to_string(), + ]); + let settings = must_build_settings(matches.subcommand_matches("run").unwrap()); + let sess = Arc::new(ParseSession::default()); + match exec_program(sess.clone(), &settings.try_into().unwrap()) { + Ok(_) => panic!("unreachable code."), + Err(msg) => { + assert!(msg + .contains("attribute 'a' not found in schema 'Person', did you mean '[\"aa\"]'?")) + } + } +} + +#[test] +fn test_error_message_fuzz_unmatched() { + let test_case_path = PathBuf::from("./src/test_data/fuzz_match/main_unmatched.k"); + let matches = app().arg_required_else_help(true).get_matches_from(&[ + ROOT_CMD, + "run", + &test_case_path.canonicalize().unwrap().display().to_string(), + ]); + let settings = must_build_settings(matches.subcommand_matches("run").unwrap()); + let sess = Arc::new(ParseSession::default()); + match exec_program(sess.clone(), &settings.try_into().unwrap()) { + Ok(_) => panic!("unreachable code."), + Err(msg) => { + assert!(msg.contains("attribute 'a' not found in schema 'Person'")) + } + } +} diff --git a/kclvm/sema/Cargo.toml b/kclvm/sema/Cargo.toml index c5dba4319..84cd7aba8 100644 --- a/kclvm/sema/Cargo.toml +++ b/kclvm/sema/Cargo.toml @@ -29,6 +29,7 @@ compiler_base_span = {path = "../../compiler_base/span", version = "0.0.2"} compiler_base_session = {path = "../../compiler_base/session"} compiler_base_macros = "0.0.1" compiler_base_error = "0.0.8" +suggestions = "0.1.1" [dev-dependencies] kclvm-parser = {path = "../parser"} diff --git a/kclvm/sema/src/resolver/attr.rs b/kclvm/sema/src/resolver/attr.rs index 3e22f4d3c..229110f77 100644 --- a/kclvm/sema/src/resolver/attr.rs +++ b/kclvm/sema/src/resolver/attr.rs @@ -3,6 +3,7 @@ use std::rc::Rc; use crate::builtin::system_module::{get_system_module_members, UNITS, UNITS_NUMBER_MULTIPLIER}; use crate::builtin::{get_system_member_function_ty, STRING_MEMBER_FUNCTIONS}; use crate::resolver::Resolver; +use crate::ty::TypeKind::Schema; use crate::ty::{DictType, ModuleKind, Type, TypeKind}; use kclvm_error::diagnostic::Range; use kclvm_error::*; @@ -110,16 +111,31 @@ impl<'ctx> Resolver<'ctx> { } } }; + if !result { + // The attr user input. + let (attr, suggestion) = if attr.is_empty() { + ("[missing name]", "".to_string()) + } else { + let mut suggestion = String::new(); + // Calculate the closests miss attributes. + if let Schema(schema_ty) = &obj.kind { + // Get all the attrbuets of the schema. + let attrs = schema_ty.attrs.keys().cloned().collect::>(); + let suggs = suggestions::provide_suggestions(attr, &attrs); + if suggs.len() > 0 { + suggestion = format!(", did you mean '{:?}'?", suggs); + } + } + (attr, suggestion) + }; + self.handler.add_type_error( &format!( - "{} has no attribute {}", + "attribute '{}' not found in schema '{}'{}", + attr, obj.ty_str(), - if attr.is_empty() { - "[missing name]" - } else { - attr - } + suggestion ), range, );