1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
use erg_common::style::{Attribute, Color, StyledStrings, THEME};
use erg_common::{option_enum_unwrap, switch_lang};

use crate::ty::typaram::TyParam;
use crate::ty::value::ValueObj;
use crate::ty::{HasType, Predicate, SubrKind, Type};

use crate::context::Context;

const HINT: Color = THEME.colors.hint;
const ERR: Color = THEME.colors.error;
#[cfg(not(feature = "pretty"))]
const ATTR: Attribute = Attribute::Bold;
#[cfg(feature = "pretty")]
const ATTR: Attribute = Attribute::Underline;

#[derive(PartialEq, Eq)]
enum Sequence {
    Forward,
    Backward,
}

// TODO: these should not be in Context
impl Context {
    /// TODO: custom types
    fn get_verb_and_preposition(trait_: &Type) -> Option<(&str, &str, Sequence)> {
        match &trait_.qual_name()[..] {
            "Add" => Some(("add", "and", Sequence::Forward)),
            "Sub" => Some(("subtract", "from", Sequence::Backward)),
            "Mul" => Some(("multiply", "and", Sequence::Forward)),
            "Div" => Some(("divide", "by", Sequence::Forward)),
            "Eq" => Some(("compare", "and", Sequence::Forward)),
            "Ord" => Some(("compare", "and", Sequence::Forward)),
            _ => None,
        }
    }

    pub(crate) fn get_call_type_mismatch_hint(
        &self,
        callee_t: &Type,
        attr: Option<&str>,
        nth: usize,
        expected: &Type,
        found: &Type,
    ) -> Option<String> {
        if &callee_t.qual_name()[..] == "Array" && attr == Some("__getitem__") && nth == 1 {
            let len = &callee_t.typarams().get(1).cloned()?;
            let (_, _, preds) = found.clone().deconstruct_refinement().ok()?;
            if let Predicate::Equal {
                lhs: _,
                rhs: accessed,
            } = preds.iter().next()?
            {
                let accessed = if let TyParam::Value(value) = accessed {
                    value
                        .clone()
                        .try_add(ValueObj::Nat(1))
                        .map(TyParam::Value)
                        .unwrap_or_else(|| accessed.clone())
                } else {
                    accessed.clone()
                };
                return Some(switch_lang! {
                    "japanese" => format!("配列の長さは{len}ですが、{accessed}番目の要素にアクセスしようとしています"),
                    "simplified_chinese" => format!("数组长度为{len}但尝试访问第{accessed}个元素"),
                    "traditional_chinese" => format!("陣列長度為{len}但嘗試訪問第{accessed}個元素"),
                    "english" => format!("Array length is {len} but tried to access the {accessed}th element"),
                });
            }
        }
        self.get_simple_type_mismatch_hint(expected, found)
    }

    pub(crate) fn get_simple_type_mismatch_hint(
        &self,
        expected: &Type,
        found: &Type,
    ) -> Option<String> {
        let expected = if let Type::FreeVar(fv) = expected {
            if fv.is_linked() {
                fv.crack().clone()
            } else {
                let (_sub, sup) = fv.get_subsup()?;
                sup
            }
        } else {
            expected.clone()
        };
        let mut hint = StyledStrings::default();

        if let (Type::Subr(expt), Type::Subr(fnd)) = (&expected, &found) {
            if let (SubrKind::Func, SubrKind::Proc) = (expt.kind, fnd.kind) {
                switch_lang!(
                    "japanese" => {
                        hint.push_str("この仮引数は(副作用のない)関数を受け取りますが、プロシージャは副作用があるため受け取りません。副作用を取り除き、");
                        hint.push_str_with_color_and_attribute("=>", ERR, ATTR);
                        hint.push_str("の代わりに");
                        hint.push_str_with_color_and_attribute("->", HINT, ATTR);
                        hint.push_str("を使用する必要があります");
                    },
                    "simplified_chinese" => {
                        hint.push_str("此参数接受func(无副作用)但由于副作用而不接受proc。你应该使用 ");
                        hint.push_str_with_color_and_attribute("->", HINT, ATTR);
                        hint.push_str("而不是 ");
                        hint.push_str_with_color_and_attribute("=>", ERR, ATTR);
                    },
                    "traditional_chinese" => {
                        hint.push_str("此参数接受 func(无副作用)但由于副作用而不接受proc。你應該使用 ");
                        hint.push_str_with_color_and_attribute("->", HINT, ATTR);
                        hint.push_str("而不是 ");
                        hint.push_str_with_color_and_attribute("=>", ERR, ATTR);
                    },
                    "english" => {
                        hint.push_str("This param accepts func (without side-effects) but not proc because of side-effects. You should use ");
                        hint.push_str_with_color_and_attribute("->", HINT, ATTR);
                        hint.push_str(" instead of ");
                        hint.push_str_with_color_and_attribute("=>", ERR, ATTR);
                    },
                );
                return Some(hint.to_string());
            }
        }

        match (&expected.qual_name()[..], &found.qual_name()[..]) {
            ("Eq", "Float") => {
                switch_lang!(
                    "japanese" => {
                        hint.push_str("Floatは等価関係が定義されていません。");
                        hint.push_str_with_color_and_attribute("l == R", ERR, ATTR);
                        hint.push_str("ではなく、");
                        hint.push_str_with_color_and_attribute("l - r <= Float.EPSILON", HINT, ATTR);
                        hint.push_str("を使用してください");
                    },
                    "simplified_chinese" => {
                        hint.push_str("Float没有定义等价关系。你应该使用");
                        hint.push_str_with_color_and_attribute("l == R", ERR, ATTR);
                        hint.push_str("而不是");
                        hint.push_str_with_color_and_attribute("l - r <= Float.EPSILON", HINT, ATTR);
                    },
                    "traditional_chinese" => {
                        hint.push_str("Float沒有定義等價關係。你應該使用");
                        hint.push_str_with_color_and_attribute("l == R", ERR, ATTR);
                        hint.push_str(" instead of ");
                        hint.push_str_with_color_and_attribute("l - r <= Float.EPSILON", HINT, ATTR);
                    },
                    "english" => {
                        hint.push_str("Float has no equivalence relation defined. you should use ");
                        hint.push_str_with_color_and_attribute("l == R", ERR, ATTR);
                        hint.push_str(" instead of ");
                        hint.push_str_with_color_and_attribute("l - r <= Float.EPSILON", HINT, ATTR);
                    },
                );
                Some(hint.to_string())
            }
            _ => {
                let (verb, preposition, _sequence) = Self::get_verb_and_preposition(&expected)?;
                found
                    .union_types()
                    .map(|(t1, t2)| format!("cannot {verb} {t1} {preposition} {t2}"))
                    .or_else(|| {
                        expected.inner_ts().get(0).map(|expected_inner| {
                            let expected_inner = self.readable_type(expected_inner.clone(), false);
                            format!("cannot {verb} {found} {preposition} {expected_inner}")
                        })
                    })
            }
        }
    }

    pub(crate) fn get_no_candidate_hint(&self, proj: &Type) -> Option<String> {
        match proj {
            Type::Proj { lhs, rhs: _ } => {
                if let Type::FreeVar(fv) = lhs.as_ref() {
                    let (sub, sup) = fv.get_subsup()?;
                    let (verb, preposition, sequence) = Self::get_verb_and_preposition(&sup)?;
                    let sup = *option_enum_unwrap!(sup.typarams().get(0)?.clone(), TyParam::Type)?;
                    let sup = self.readable_type(sup, false);
                    let (l, r) = if sequence == Sequence::Forward {
                        (sub, sup)
                    } else {
                        (sup, sub)
                    };
                    Some(format!("cannot {verb} {l} {preposition} {r}"))
                } else {
                    None
                }
            }
            _ => None,
        }
    }
}