erg_compiler/context/
hint.rs

1use erg_common::dict::Dict;
2use erg_common::style::{Attribute, Color, StyledStrings, THEME};
3use erg_common::{option_enum_unwrap, switch_lang};
4
5use crate::ty::typaram::TyParam;
6use crate::ty::value::ValueObj;
7use crate::ty::{Field, HasType, Predicate, SubrKind, SubrType, Type};
8
9use crate::context::Context;
10
11const HINT: Color = THEME.colors.hint;
12const ERR: Color = THEME.colors.error;
13#[cfg(not(feature = "pretty"))]
14const ATTR: Attribute = Attribute::Bold;
15#[cfg(feature = "pretty")]
16const ATTR: Attribute = Attribute::Underline;
17
18#[derive(PartialEq, Eq)]
19enum Sequence {
20    Forward,
21    Backward,
22}
23
24// TODO: these should not be in Context
25impl Context {
26    /// TODO: custom types
27    fn get_verb_and_preposition(trait_: &Type) -> Option<(&str, &str, Sequence)> {
28        match &trait_.qual_name()[..] {
29            "Add" => Some(("add", "and", Sequence::Forward)),
30            "Sub" => Some(("subtract", "from", Sequence::Backward)),
31            "Mul" => Some(("multiply", "and", Sequence::Forward)),
32            "Div" => Some(("divide", "by", Sequence::Forward)),
33            "Eq" => Some(("compare", "and", Sequence::Forward)),
34            "Ord" => Some(("compare", "and", Sequence::Forward)),
35            _ => None,
36        }
37    }
38
39    pub(crate) fn get_call_type_mismatch_hint(
40        &self,
41        callee_t: &Type,
42        attr: Option<&str>,
43        nth: usize,
44        expected: &Type,
45        found: &Type,
46    ) -> Option<String> {
47        if self.cfg.fast_error_report {
48            return None;
49        }
50        if &callee_t.qual_name()[..] == "List" && attr == Some("__getitem__") && nth == 1 {
51            let len = &callee_t.typarams().get(1).cloned()?;
52            let (_, _, pred) = found.clone().deconstruct_refinement().ok()?;
53            if let Predicate::Equal { rhs: accessed, .. } = pred {
54                let accessed = if let TyParam::Value(value) = &accessed {
55                    value
56                        .clone()
57                        .try_add(ValueObj::Nat(1))
58                        .map(TyParam::Value)
59                        .unwrap_or_else(|| accessed.clone())
60                } else {
61                    accessed.clone()
62                };
63                return Some(switch_lang! {
64                    "japanese" => format!("リストの長さは{len}ですが、{accessed}番目の要素にアクセスしようとしています"),
65                    "simplified_chinese" => format!("数组长度为{len}但尝试访问第{accessed}个元素"),
66                    "traditional_chinese" => format!("陣列長度為{len}但嘗試訪問第{accessed}個元素"),
67                    "english" => format!("List length is {len} but tried to access the {accessed}th element"),
68                });
69            }
70        }
71        self.get_simple_type_mismatch_hint(expected, found)
72    }
73
74    pub(crate) fn get_simple_type_mismatch_hint(
75        &self,
76        expected: &Type,
77        found: &Type,
78    ) -> Option<String> {
79        if self.cfg.fast_error_report {
80            return None;
81        }
82        let expected = if let Some(fv) = expected.as_free() {
83            if fv.is_linked() {
84                fv.crack().clone()
85            } else {
86                let (_sub, sup) = fv.get_subsup()?;
87                sup
88            }
89        } else {
90            expected.clone()
91        };
92        let mut hint = StyledStrings::default();
93        match (&expected, &found) {
94            (Type::Subr(expt), Type::Subr(fnd)) => {
95                if let Some(hint) = self.get_subr_type_mismatch_hint(expt, fnd) {
96                    return Some(hint);
97                }
98            }
99            (Type::Quantified(expt), Type::Subr(fnd)) => {
100                if let Type::Subr(expt) = expt.as_ref() {
101                    if let Some(hint) = self.get_subr_type_mismatch_hint(expt, fnd) {
102                        return Some(hint);
103                    }
104                }
105            }
106            (Type::Quantified(expt), Type::Quantified(fnd)) => {
107                if let (Type::Subr(expt), Type::Subr(fnd)) = (expt.as_ref(), fnd.as_ref()) {
108                    if let Some(hint) = self.get_subr_type_mismatch_hint(expt, fnd) {
109                        return Some(hint);
110                    }
111                }
112            }
113            (Type::Record(expt), Type::Record(fnd)) => {
114                if let Some(hint) = self.get_record_type_mismatch_hint(expt, fnd) {
115                    return Some(hint);
116                }
117            }
118            (Type::NamedTuple(expt), Type::NamedTuple(fnd)) => {
119                let expt = Dict::from(expt.clone());
120                let fnd = Dict::from(fnd.clone());
121                if let Some(hint) = self.get_record_type_mismatch_hint(&expt, &fnd) {
122                    return Some(hint);
123                }
124            }
125            (Type::And(tys, _), found) if tys.len() == 2 => {
126                let mut iter = tys.iter();
127                let l = iter.next().unwrap();
128                let r = iter.next().unwrap();
129                let left = self.readable_type(l.clone());
130                let right = self.readable_type(r.clone());
131                if self.supertype_of(l, found) {
132                    let msg = switch_lang!(
133                        "japanese" => format!("型{found}は{left}のサブタイプですが、{right}のサブタイプではありません"),
134                        "simplified_chinese" => format!("类型{found}是{left}的子类型但不是{right}的子类型"),
135                        "traditional_chinese" => format!("型別{found}是{left}的子型別但不是{right}的子型別"),
136                        "english" => format!("Type {found} is a subtype of {left} but not of {right}"),
137                    );
138                    hint.push_str(&msg);
139                    return Some(hint.to_string());
140                } else if self.supertype_of(r, found) {
141                    let msg = switch_lang!(
142                        "japanese" => format!("型{found}は{right}のサブタイプですが、{left}のサブタイプではありません"),
143                        "simplified_chinese" => format!("类型{found}是{right}的子类型但不是{left}的子类型"),
144                        "traditional_chinese" => format!("型別{found}是{right}的子型別但不是{left}の子型別"),
145                        "english" =>format!("Type {found} is a subtype of {right} but not of {left}"),
146                    );
147                    hint.push_str(&msg);
148                    return Some(hint.to_string());
149                }
150            }
151            _ => {}
152        }
153
154        match (&expected.qual_name()[..], &found.qual_name()[..]) {
155            ("Eq", "Float") => {
156                switch_lang!(
157                    "japanese" => {
158                        hint.push_str("Floatは等価関係が定義されていません。");
159                        hint.push_str_with_color_and_attr("l == r", ERR, ATTR);
160                        hint.push_str("ではなく、");
161                        hint.push_str_with_color_and_attr("l - r <= Float.EPSILON", HINT, ATTR);
162                        hint.push_str("あるいは");
163                        hint.push_str_with_color_and_attr("l.nearly_eq(r)", HINT, ATTR);
164                        hint.push_str("を使用してください");
165                    },
166                    "simplified_chinese" => {
167                        hint.push_str("Float没有定义等价关系。你应该使用");
168                        hint.push_str_with_color_and_attr("l - r <= Float.EPSILON", HINT, ATTR);
169                        hint.push_str("或者");
170                        hint.push_str_with_color_and_attr("l.nearly_eq(r)", HINT, ATTR);
171                        hint.push_str("而不是");
172                        hint.push_str_with_color_and_attr("l == r", ERR, ATTR);
173                    },
174                    "traditional_chinese" => {
175                        hint.push_str("Float沒有定義等價關係。你應該使用");
176                        hint.push_str_with_color_and_attr("l - r <= Float.EPSILON", HINT, ATTR);
177                        hint.push_str("或者");
178                        hint.push_str_with_color_and_attr("l.nearly_eq(r)", HINT, ATTR);
179                        hint.push_str("而不是");
180                        hint.push_str_with_color_and_attr("l == r", ERR, ATTR);
181                    },
182                    "english" => {
183                        hint.push_str("Float has no equivalence relation defined. You should use ");
184                        hint.push_str_with_color_and_attr("l - r <= Float.EPSILON", HINT, ATTR);
185                        hint.push_str(" or ");
186                        hint.push_str_with_color_and_attr("l.nearly_eq(r)", HINT, ATTR);
187                        hint.push_str(" instead of ");
188                        hint.push_str_with_color_and_attr("l == r", ERR, ATTR);
189                    },
190                );
191                Some(hint.to_string())
192            }
193            _ => {
194                let (verb, preposition, _sequence) = Self::get_verb_and_preposition(&expected)?;
195                found
196                    .union_pair()
197                    .map(|(t1, t2)| format!("cannot {verb} {t1} {preposition} {t2}"))
198                    .or_else(|| {
199                        expected.inner_ts().first().map(|expected_inner| {
200                            let expected_inner = self.readable_type(expected_inner.clone());
201                            format!("cannot {verb} {found} {preposition} {expected_inner}")
202                        })
203                    })
204            }
205        }
206    }
207
208    fn get_record_type_mismatch_hint(
209        &self,
210        expected: &Dict<Field, Type>,
211        found: &Dict<Field, Type>,
212    ) -> Option<String> {
213        let missing = expected.clone().diff(found);
214        if missing.is_empty() {
215            let mut mismatched = "".to_string();
216            for (field, expected) in expected.iter() {
217                if let Some(found) = found.get(field) {
218                    if !self.supertype_of(expected, found) {
219                        if !mismatched.is_empty() {
220                            mismatched.push_str(", ");
221                        }
222                        mismatched.push_str(&format!("{field}: {expected} but found {found}"));
223                    }
224                }
225            }
226            if mismatched.is_empty() {
227                None
228            } else {
229                Some(mismatched)
230            }
231        } else {
232            let mut hint = "missing: ".to_string();
233            for (i, (field, typ)) in missing.iter().enumerate() {
234                if i > 0 {
235                    hint.push_str(", ");
236                }
237                hint.push_str(&format!("{field}: {typ}"));
238            }
239            Some(hint)
240        }
241    }
242
243    // TODO: parameter type mismatches
244    fn get_subr_type_mismatch_hint(&self, expected: &SubrType, found: &SubrType) -> Option<String> {
245        let mut hint = StyledStrings::default();
246        if let (SubrKind::Func, SubrKind::Proc) = (expected.kind, found.kind) {
247            switch_lang!(
248                "japanese" => {
249                    hint.push_str("この仮引数は(副作用のない)関数を受け取りますが、プロシージャは副作用があるため受け取りません。副作用を取り除き、");
250                    hint.push_str_with_color_and_attr("=>", ERR, ATTR);
251                    hint.push_str("の代わりに");
252                    hint.push_str_with_color_and_attr("->", HINT, ATTR);
253                    hint.push_str("を使用する必要があります");
254                },
255                "simplified_chinese" => {
256                    hint.push_str("此参数接受函数(无副作用),但不接受过程,因为过程有副作用。你应该使用");
257                    hint.push_str_with_color_and_attr("=>", HINT, ATTR);
258                    hint.push_str("而不是");
259                    hint.push_str_with_color_and_attr("->", ERR, ATTR);
260                },
261                "traditional_chinese" => {
262                    hint.push_str("此參數接受函數(無副作用),但不接受過程,因為過程有副作用。你應該使用");
263                    hint.push_str_with_color_and_attr("=>", HINT, ATTR);
264                    hint.push_str("而不是");
265                    hint.push_str_with_color_and_attr("->", ERR, ATTR);
266                },
267                "english" => {
268                    hint.push_str("This param accepts func (without side-effects) but not proc because of side-effects. You should use ");
269                    hint.push_str_with_color_and_attr("=>", HINT, ATTR);
270                    hint.push_str(" instead of ");
271                    hint.push_str_with_color_and_attr("->", ERR, ATTR);
272                },
273            );
274            return Some(hint.to_string());
275        }
276        if let Some((expect, _found)) = expected
277            .non_var_params()
278            .zip(found.non_var_params())
279            .find(|(expect, found)| expect.typ().is_ref() && !found.typ().is_ref())
280        {
281            let hint = switch_lang!(
282                "japanese" => format!("{expect}は参照を受け取るよう宣言されましたが、実体が渡されています(refプレフィックスを追加してください)"),
283                "simplified_chinese" => format!("{expect}被声明为接受引用,但实体被传递(请添加ref前缀)"),
284                "traditional_chinese" => format!("{expect}被宣告為接受引用,但實體被傳遞(請添加ref前綴)"),
285                "english" => format!("{expect} is declared as a reference parameter but definition is an owned parameter (add `ref` prefix)"),
286            );
287            return Some(hint);
288        }
289        None
290    }
291
292    pub(crate) fn get_no_candidate_hint(&self, proj: &Type) -> Option<String> {
293        if self.cfg.fast_error_report {
294            return None;
295        }
296        match proj {
297            Type::Proj { lhs, rhs: _ } => {
298                if let Some(fv) = lhs.as_free() {
299                    let (sub, sup) = fv.get_subsup()?;
300                    let (verb, preposition, sequence) = Self::get_verb_and_preposition(&sup)?;
301                    let sup = *option_enum_unwrap!(sup.typarams().first()?.clone(), TyParam::Type)?;
302                    let sup = self.readable_type(sup);
303                    let (l, r) = if sequence == Sequence::Forward {
304                        (sub, sup)
305                    } else {
306                        (sup, sub)
307                    };
308                    Some(format!("cannot {verb} {l} {preposition} {r}"))
309                } else {
310                    None
311                }
312            }
313            _ => None,
314        }
315    }
316}