go_types/check/
assignment.rs

1// Copyright 2022 The Goscript Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4//
5//
6// This code is adapted from the offical Go code
7//
8// Copyright 2013 The Go Authors. All rights reserved.
9// Use of this source code is governed by a BSD-style
10// license that can be found in the LICENSE file.
11
12#![allow(dead_code)]
13use crate::SourceRead;
14
15use super::super::obj::EntityType;
16use super::super::objects::{ObjKey, TypeKey};
17use super::super::operand::{Operand, OperandMode};
18use super::super::typ;
19use super::check::{Checker, FilesContext};
20use super::util::UnpackResult;
21use go_parser::ast::Expr;
22use go_parser::ast::Node;
23use go_parser::Pos;
24
25impl<'a, S: SourceRead> Checker<'a, S> {
26    /// assignment reports whether x can be assigned to a variable of type t,
27    /// if necessary by attempting to convert untyped values to the appropriate
28    /// type. context describes the context in which the assignment takes place.
29    /// Use t == None to indicate assignment to an untyped blank identifier.
30    /// x.mode is set to invalid if the assignment failed.
31    pub fn assignment(
32        &mut self,
33        x: &mut Operand,
34        t: Option<TypeKey>,
35        note: &str,
36        fctx: &mut FilesContext<S>,
37    ) {
38        self.single_value(x);
39        if x.invalid() {
40            return;
41        }
42
43        match x.mode {
44            OperandMode::Constant(_)
45            | OperandMode::Variable
46            | OperandMode::MapIndex
47            | OperandMode::Value
48            | OperandMode::CommaOk => {}
49            _ => unreachable!(),
50        }
51
52        let xt = x.typ.unwrap();
53        if typ::is_untyped(xt, self.tc_objs) {
54            if t.is_none() && xt == self.basic_type(typ::BasicType::UntypedNil) {
55                self.error(
56                    x.pos(self.ast_objs),
57                    format!("use of untyped nil in {}", note),
58                );
59                x.mode = OperandMode::Invalid;
60                return;
61            }
62            // spec: "If an untyped constant is assigned to a variable of interface
63            // type or the blank identifier, the constant is first converted to type
64            // bool, rune, int, float64, complex128 or string respectively, depending
65            // on whether the value is a boolean, rune, integer, floating-point, complex,
66            // or string constant."
67            let target = if t.is_none() || typ::is_interface(t.unwrap(), self.tc_objs) {
68                typ::untyped_default_type(xt, self.tc_objs)
69            } else {
70                t.unwrap()
71            };
72            self.convert_untyped(x, target, fctx);
73            if x.invalid() {
74                return;
75            }
76        }
77        // x.typ is typed
78
79        // spec: "If a left-hand side is the blank identifier, any typed or
80        // non-constant value except for the predeclared identifier nil may
81        // be assigned to it."
82        if t.is_none() {
83            return;
84        }
85
86        let mut reason = String::new();
87        if !x.assignable_to(t.unwrap(), Some(&mut reason), self, fctx) {
88            let xd = self.new_dis(x);
89            let td = self.new_dis(t.as_ref().unwrap());
90            if reason.is_empty() {
91                self.error(
92                    xd.pos(),
93                    format!("cannot use {} as {} value in {}", xd, td, note),
94                );
95            } else {
96                self.error(
97                    xd.pos(),
98                    format!("cannot use {} as {} value in {}: {}", xd, td, note, reason),
99                );
100            }
101            x.mode = OperandMode::Invalid;
102        }
103    }
104
105    pub fn init_const(&mut self, lhskey: ObjKey, x: &mut Operand, fctx: &mut FilesContext<S>) {
106        let invalid_type = self.invalid_type();
107        let lhs = self.lobj_mut(lhskey);
108        if x.invalid() || x.typ == Some(invalid_type) {
109            lhs.set_type(Some(invalid_type));
110        }
111        if lhs.typ() == Some(invalid_type) {
112            return;
113        }
114
115        // If the lhs doesn't have a type yet, use the type of x.
116        let lhs = self.lobj_mut(lhskey);
117        if lhs.typ().is_none() {
118            lhs.set_type(x.typ);
119        }
120        // rhs must be a constant
121        if let OperandMode::Constant(_) = &x.mode {
122            debug_assert!(typ::is_const_type(x.typ.unwrap(), self.tc_objs));
123            let t = self.lobj(lhskey).typ();
124            self.assignment(x, t, "constant declaration", fctx);
125            if x.mode != OperandMode::Invalid {
126                self.lobj_mut(lhskey)
127                    .set_const_val(x.mode.constant_val().unwrap().clone());
128            }
129        } else {
130            let dis = self.new_dis(x);
131            self.error(dis.pos(), format!("{} is not constant", dis));
132        }
133    }
134
135    pub fn init_var(
136        &mut self,
137        lhskey: ObjKey,
138        x: &mut Operand,
139        msg: &str,
140        fctx: &mut FilesContext<S>,
141    ) -> Option<TypeKey> {
142        let invalid_type = self.invalid_type();
143        let lhs = self.lobj_mut(lhskey);
144        if x.invalid() || x.typ == Some(invalid_type) || lhs.typ() == Some(invalid_type) {
145            if lhs.typ().is_none() {
146                lhs.set_type(Some(invalid_type));
147            }
148            return None;
149        }
150        // If the lhs doesn't have a type yet, use the type of x.
151        if lhs.typ().is_none() {
152            let xt = x.typ.unwrap();
153            let lhs_type = if typ::is_untyped(xt, self.tc_objs) {
154                // convert untyped types to default types
155                if xt == self.basic_type(typ::BasicType::UntypedNil) {
156                    self.error(
157                        x.pos(self.ast_objs),
158                        format!("use of untyped nil in {}", msg),
159                    );
160                    invalid_type
161                } else {
162                    typ::untyped_default_type(xt, self.tc_objs)
163                }
164            } else {
165                xt
166            };
167
168            self.lobj_mut(lhskey).set_type(Some(lhs_type));
169            if lhs_type == invalid_type {
170                return None;
171            }
172        }
173        let t = self.lobj(lhskey).typ().clone();
174        self.assignment(x, t, msg, fctx);
175        if x.mode != OperandMode::Invalid {
176            x.typ
177        } else {
178            None
179        }
180    }
181
182    pub fn assign_var(
183        &mut self,
184        lhs: &Expr,
185        x: &mut Operand,
186        fctx: &mut FilesContext<S>,
187    ) -> Option<TypeKey> {
188        let invalid_type = self.invalid_type();
189        if x.invalid() || x.typ == Some(invalid_type) {
190            return None;
191        }
192
193        let mut v: Option<ObjKey> = None;
194        let mut v_used = false;
195        // determine if the lhs is a (possibly parenthesized) identifier.
196        if let Expr::Ident(ikey) = Checker::<S>::unparen(lhs) {
197            let name = &self.ast_ident(*ikey).name;
198            if name == "_" {
199                self.result.record_def(*ikey, None);
200                self.assignment(x, None, "assignment to _ identifier", fctx);
201                return if x.mode != OperandMode::Invalid {
202                    x.typ
203                } else {
204                    None
205                };
206            } else {
207                // If the lhs is an identifier denoting a variable v, this assignment
208                // is not a 'use' of v. Remember current value of v.used and restore
209                // after evaluating the lhs via check.expr.
210                if let Some(okey) = self.lookup(name) {
211                    // It's ok to mark non-local variables, but ignore variables
212                    // from other packages to avoid potential race conditions with
213                    // dot-imported variables.
214                    if let EntityType::Var(prop) = self.lobj(okey).entity_type() {
215                        v = Some(okey);
216                        v_used = prop.used;
217                    }
218                }
219            }
220        }
221
222        let mut z = Operand::new();
223        self.expr(&mut z, lhs, fctx);
224        if let Some(okey) = v {
225            self.lobj_mut(okey)
226                .entity_type_mut()
227                .var_property_mut()
228                .used = v_used; // restore v.used
229        }
230
231        if z.mode == OperandMode::Invalid || z.typ == Some(invalid_type) {
232            return None;
233        }
234
235        // spec: "Each left-hand side operand must be addressable, a map index
236        // expression, or the blank identifier. Operands may be parenthesized."
237        match z.mode {
238            OperandMode::Invalid => unreachable!(),
239            OperandMode::Variable | OperandMode::MapIndex => {}
240            _ => {
241                if let Some(expr) = &z.expr {
242                    if let Expr::Selector(sexpr) = expr {
243                        let mut op = Operand::new();
244                        self.expr(&mut op, &sexpr.expr, fctx);
245                        if op.mode == OperandMode::MapIndex {
246                            let ed = self.new_dis(expr);
247                            self.error(
248                                ed.pos(),
249                                format!("cannot assign to struct field {} in map", ed),
250                            );
251                            return None;
252                        }
253                    }
254                }
255                let dis = self.new_dis(&z);
256                self.error(dis.pos(), format!("cannot assign to {}", dis));
257                return None;
258            }
259        }
260
261        self.assignment(x, z.typ, "assignment", fctx);
262        if x.mode != OperandMode::Invalid {
263            x.typ
264        } else {
265            None
266        }
267    }
268
269    /// If return_pos is_some, init_vars is called to type-check the assignment of
270    /// return expressions, and return_pos is the position of the return statement.
271    pub fn init_vars(
272        &mut self,
273        lhs: &Vec<ObjKey>,
274        rhs: &Vec<Expr>,
275        return_pos: Option<Pos>,
276        fctx: &mut FilesContext<S>,
277    ) {
278        let invalid_type = self.invalid_type();
279        let ll = lhs.len();
280        // requires return_pos.is_none for this:
281        // func() (int, bool) {
282        //    var m map[int]int
283        //    return /* ERROR "wrong number of return values" */ m[0]
284        // }
285        let result = self.unpack(rhs, ll, ll == 2 && return_pos.is_none(), false, fctx);
286        let mut invalidate_lhs = || {
287            for okey in lhs.iter() {
288                let lobj = self.lobj_mut(*okey);
289                if lobj.typ().is_none() {
290                    lobj.set_type(Some(invalid_type));
291                }
292            }
293        };
294
295        match result {
296            UnpackResult::Error => invalidate_lhs(),
297            UnpackResult::Tuple(_, _, _)
298            | UnpackResult::CommaOk(_, _)
299            | UnpackResult::Mutliple(_, _)
300            | UnpackResult::Single(_, _)
301            | UnpackResult::Nothing(_) => match result.rhs_count() {
302                (count, std::cmp::Ordering::Greater) | (count, std::cmp::Ordering::Less) => {
303                    invalidate_lhs();
304                    result.use_(self, 0, fctx);
305                    if let Some(p) = return_pos {
306                        self.error(
307                            p,
308                            format!("wrong number of return values (want {}, got {})", ll, count),
309                        )
310                    } else {
311                        self.error(
312                            rhs[0].pos(self.ast_objs),
313                            format!("cannot initialize {} variables with {} values", ll, count),
314                        )
315                    }
316                    return;
317                }
318                _ => {
319                    let context = if return_pos.is_some() {
320                        "return statement"
321                    } else {
322                        "assignment"
323                    };
324                    for (i, l) in lhs.iter().enumerate() {
325                        let mut x = Operand::new();
326                        result.get(self, &mut x, i, fctx);
327                        self.init_var(*l, &mut x, context, fctx);
328                    }
329                }
330            },
331        }
332        if let UnpackResult::CommaOk(e, types) = result {
333            self.result.record_comma_ok_types::<S>(
334                e.as_ref().unwrap(),
335                &types,
336                self.tc_objs,
337                self.ast_objs,
338                self.pkg,
339            );
340        }
341    }
342
343    pub fn assign_vars(&mut self, lhs: &Vec<Expr>, rhs: &Vec<Expr>, fctx: &mut FilesContext<S>) {
344        let ll = lhs.len();
345        let result = self.unpack(rhs, ll, ll == 2, false, fctx);
346        match result {
347            UnpackResult::Error => self.use_lhs(lhs, fctx),
348            UnpackResult::Tuple(_, _, _)
349            | UnpackResult::CommaOk(_, _)
350            | UnpackResult::Mutliple(_, _)
351            | UnpackResult::Single(_, _)
352            | UnpackResult::Nothing(_) => match result.rhs_count() {
353                (count, std::cmp::Ordering::Greater) | (count, std::cmp::Ordering::Less) => {
354                    result.use_(self, 0, fctx);
355                    self.error(
356                        rhs[0].pos(self.ast_objs),
357                        format!("cannot assign {} values to {} variables", count, ll),
358                    );
359                    return;
360                }
361                _ => {
362                    for (i, l) in lhs.iter().enumerate() {
363                        let mut x = Operand::new();
364                        result.get(self, &mut x, i, fctx);
365                        self.assign_var(l, &mut x, fctx);
366                    }
367                }
368            },
369        }
370        if let UnpackResult::CommaOk(e, types) = result {
371            self.result.record_comma_ok_types::<S>(
372                e.as_ref().unwrap(),
373                &types,
374                self.tc_objs,
375                self.ast_objs,
376                self.pkg,
377            );
378        }
379    }
380
381    pub fn short_var_decl(
382        &mut self,
383        lhs: &Vec<Expr>,
384        rhs: &Vec<Expr>,
385        pos: Pos,
386        fctx: &mut FilesContext<S>,
387    ) {
388        let top = fctx.delayed_count();
389        let scope_key = self.octx.scope.unwrap();
390        let mut new_vars = Vec::new();
391        let lhs_vars = lhs
392            .iter()
393            .map(|x| {
394                if let Expr::Ident(ikey) = x {
395                    // Use the correct obj if the ident is redeclared. The
396                    // variable's scope starts after the declaration; so we
397                    // must use Scope.lookup here and call Scope.Insert
398                    // (via Check.declare) later.
399                    let ident = self.ast_ident(*ikey);
400                    if let Some(okey) = self.tc_objs.scopes[scope_key].lookup(&ident.name) {
401                        self.result.record_use(*ikey, *okey);
402                        if self.lobj(*okey).entity_type().is_var() {
403                            *okey
404                        } else {
405                            let pos = x.pos(self.ast_objs);
406                            self.error(pos, format!("cannot assign to {}", self.new_dis(x)));
407                            // dummy variable
408                            self.tc_objs
409                                .new_var(pos, Some(self.pkg), "_".to_owned(), None)
410                        }
411                    } else {
412                        // declare new variable, possibly a blank (_) variable
413                        let (pos, pkg, name) = (ident.pos, Some(self.pkg), ident.name.clone());
414                        let okey = self.tc_objs.new_var(pos, pkg, name.clone(), None);
415                        if name != "_" {
416                            new_vars.push(okey);
417                        }
418                        self.result.record_def(*ikey, Some(okey));
419                        okey
420                    }
421                } else {
422                    self.use_lhs(&vec![x.clone()], fctx);
423                    let pos = x.pos(self.ast_objs);
424                    self.error(pos, format!("cannot declare {}", self.new_dis(x)));
425                    // dummy variable
426                    self.tc_objs
427                        .new_var(pos, Some(self.pkg), "_".to_owned(), None)
428                }
429            })
430            .collect();
431
432        self.init_vars(&lhs_vars, rhs, None, fctx);
433
434        // process function literals in rhs expressions before scope changes
435        fctx.process_delayed(top, self);
436
437        // declare new variables
438        if new_vars.len() > 0 {
439            // spec: "The scope of a constant or variable identifier declared inside
440            // a function begins at the end of the ConstSpec or VarSpec (ShortVarDecl
441            // for short variable declarations) and ends at the end of the innermost
442            // containing block."
443            let scope_pos = rhs[rhs.len() - 1].end(self.ast_objs);
444            for okey in new_vars.iter() {
445                self.declare(scope_key, None, *okey, scope_pos);
446            }
447        } else {
448            self.soft_error(pos, "no new variables on left side of :=".to_owned());
449        }
450    }
451}