goscript-types 0.1.0

goscript types
Documentation
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
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
#![allow(dead_code)]
use super::obj;
use super::objects::{ObjKey, PackageKey, TCObjects, TypeKey};
use super::selection::*;
use super::typ;
use std::collections::{HashMap, HashSet};
use std::fmt;
use std::fmt::Write;

macro_rules! lookup_on_found {
    ($indices:ident, $i:ident, $target:expr, $et:ident, $indirect:ident, $found:expr) => {
        $indices = concat_vec($indices, $i);
        if $target.is_some() || $et.multiples {
            return LookupResult::Ambiguous($indices.unwrap());
        }
        *$target = Some($found);
        $indirect = $et.indirect;
    };
}

/// the result of lookup_field_or_method
#[derive(PartialEq)]
pub enum LookupResult {
    /// valid entry
    Entry(ObjKey, Vec<usize>, bool),
    /// the index sequence points to an ambiguous entry
    /// (the same name appeared more than once at the same embedding level).
    Ambiguous(Vec<usize>),
    /// a method with a pointer receiver type was found
    /// but there was no pointer on the path from the actual receiver type to
    /// the method's formal receiver base type, nor was the receiver addressable.
    BadMethodReceiver,
    /// nothing found
    NotFound,
}

pub struct MethodSet {
    list: Vec<Selection>,
}

impl MethodSet {
    pub fn new(t: &TypeKey, objs: &mut TCObjects) -> MethodSet {
        // method set up to the current depth
        let mut mset_base: HashMap<String, MethodCollision> = HashMap::new();
        let (tkey, is_ptr) = try_deref(*t, objs);
        // *typ where typ is an interface has no methods.
        if is_ptr && objs.types[tkey].try_as_interface().is_some() {
            return MethodSet { list: vec![] };
        }

        // Start with typ as single entry at shallowest depth.
        let mut current = vec![EmbeddedType::new(tkey, None, is_ptr, false)];

        // Named types that we have seen already, allocated lazily.
        // Used to avoid endless searches in case of recursive types.
        // Since only Named types can be used for recursive types, we
        // only need to track those.
        // (If we ever allow type aliases to construct recursive types,
        // we must use type identity rather than pointer equality for
        // the map key comparison, as we do in consolidate_multiples.)
        // seen is lazily initialized
        let mut seen: Option<HashSet<TypeKey>> = None;
        while !current.is_empty() {
            // embedded types found at current depth
            let mut next = vec![];
            // field and method sets at current depth
            let mut fset: HashMap<String, FieldCollision> = HashMap::new();
            let mut mset: HashMap<String, MethodCollision> = HashMap::new();
            for et in current.iter() {
                let mut tobj = &objs.types[et.typ];
                if let typ::Type::Named(detail) = tobj {
                    if seen.is_none() {
                        seen = Some(HashSet::new());
                    }
                    let seen_mut = seen.as_mut().unwrap();
                    if seen_mut.contains(&et.typ) {
                        // We have seen this type before, at a more shallow depth
                        // (note that multiples of this type at the current depth
                        // were consolidated before). The type at that depth shadows
                        // this same type at the current depth, so we can ignore
                        // this one.
                        continue;
                    }
                    seen_mut.insert(et.typ);
                    add_to_method_set(
                        &mut mset,
                        detail.methods(),
                        et.indices.as_ref().unwrap_or(&vec![]),
                        et.indirect,
                        et.multiples,
                        objs,
                    );
                    // continue with underlying type
                    tobj = &objs.types[detail.underlying()];
                }
                match tobj {
                    typ::Type::Struct(detail) => {
                        for (i, f) in detail.fields().iter().enumerate() {
                            let fobj = &objs.lobjs[*f];
                            add_to_field_set(&mut fset, f, et.multiples, objs);
                            // Embedded fields are always of the form T or *T where
                            // T is a type name. If typ appeared multiple times at
                            // this depth, f.Type appears multiple times at the next
                            // depth.
                            if fobj.var_embedded() {
                                let (tkey, is_ptr) = try_deref(fobj.typ().unwrap(), objs);
                                next.push(EmbeddedType::new(
                                    tkey,
                                    concat_vec(et.indices.clone(), i),
                                    et.indirect || is_ptr,
                                    et.multiples,
                                ))
                            }
                        }
                    }
                    typ::Type::Interface(detail) => {
                        add_to_method_set(
                            &mut mset,
                            detail.all_methods().as_ref().unwrap(),
                            et.indices.as_ref().unwrap_or(&vec![]),
                            true,
                            et.multiples,
                            objs,
                        );
                    }
                    _ => {}
                }
            }
            // Add methods and collisions at this depth to base if no entries with matching
            // names exist already.
            for (k, m) in mset.iter() {
                if !mset_base.contains_key(k) {
                    mset_base.insert(
                        k.clone(),
                        if fset.contains_key(k) {
                            MethodCollision::Collision
                        } else {
                            m.clone()
                        },
                    );
                }
            }
            // Multiple fields with matching names collide at this depth and shadow all
            // entries further down; add them as collisions to base if no entries with
            // matching names exist already.
            for (k, f) in fset.iter() {
                if *f == FieldCollision::Collision {
                    if !mset_base.contains_key(k) {
                        mset_base.insert(k.clone(), MethodCollision::Collision);
                    }
                }
            }
            current = consolidate_multiples(next, objs);
        }
        let mut list: Vec<Selection> = mset_base
            .into_iter()
            .filter_map(|(_, m)| match m {
                MethodCollision::Method(sel) => {
                    //sel.init(objs);
                    Some(sel)
                }
                MethodCollision::Collision => None,
            })
            .collect();
        list.sort_by(|a, b| a.id().cmp(b.id()));
        MethodSet { list: list }
    }

    pub fn list(&self) -> &Vec<Selection> {
        &self.list
    }

    pub fn lookup(&self, pkgkey: &PackageKey, name: &str, objs: &TCObjects) -> Option<&Selection> {
        if self.list.len() == 0 {
            return None;
        }
        let pkg = &objs.pkgs[*pkgkey];
        let id = obj::get_id(Some(pkg), name).to_string();
        self.list
            .binary_search_by_key(&&id, |x| x.id())
            .ok()
            .map(|i| &self.list[i])
    }

    pub fn fmt(&self, f: &mut fmt::Formatter<'_>, objs: &TCObjects) -> fmt::Result {
        f.write_str("MethodSet {")?;
        for field in self.list.iter() {
            field.fmt(f, objs)?;
        }
        f.write_char('}')
    }
}

/// lookup_field_or_method looks up a field or method with given package and name
/// in T and returns the corresponding *Var or *Func, an index sequence, and a
/// bool indicating if there were any pointer indirections on the path to the
/// field or method. If addressable is set, T is the type of an addressable
/// variable (only matters for method lookups).
///
/// The last index entry is the field or method index in the (possibly embedded)
/// type where the entry was found, either:
///
///	1) the list of declared methods of a named type; or
///	2) the list of all methods (method set) of an interface type; or
///	3) the list of fields of a struct type.
///
/// The earlier index entries are the indices of the embedded struct fields
/// traversed to get to the found entry, starting at depth 0.
pub fn lookup_field_or_method(
    tkey: TypeKey,
    addressable: bool,
    pkg: Option<PackageKey>,
    name: &str,
    objs: &TCObjects,
) -> LookupResult {
    if let Some(named) = &objs.types[tkey].try_as_named() {
        // Methods cannot be associated to a named pointer type
        // (spec: "The type denoted by T is called the receiver base type;
        // it must not be a pointer or interface type and it must be declared
        // in the same package as the method.").
        // Thus, if we have a named pointer type, proceed with the underlying
        // pointer type but discard the result if it is a method since we would
        // not have found it for T
        let pkey = named.underlying();
        if objs.types[pkey].try_as_pointer().is_some() {
            let re = lookup_field_or_method_impl(pkey, false, pkg, name, objs);
            if let LookupResult::Entry(okey, _, _) = &re {
                if objs.lobjs[*okey].entity_type().is_func() {
                    return LookupResult::NotFound;
                }
            }
            return re;
        }
    }
    lookup_field_or_method_impl(tkey, addressable, pkg, name, objs)
}

/// assertable_to reports whether a value of type iface can be asserted to have type t.
/// It returns None as affirmative answer. See docs for missing_method for more info
pub fn assertable_to(iface: TypeKey, t: TypeKey, objs: &TCObjects) -> Option<(ObjKey, bool)> {
    // no static check is required if T is an interface
    // spec: "If T is an interface type, x.(T) asserts that the
    //        dynamic type of x implements the interface T."
    let strict = true; // see original go code for more info
    if !strict && objs.types[t].is_interface(objs) {
        return None;
    }
    missing_method(t, iface, false, objs)
}

/// try_deref dereferences t if it is a *Pointer and returns its base.
/// Otherwise it returns t.
pub fn try_deref(t: TypeKey, objs: &TCObjects) -> (TypeKey, bool) {
    match &objs.types[t] {
        typ::Type::Pointer(detail) => (detail.base(), true),
        _ => (t, false),
    }
}

/// deref_struct_ptr dereferences typ if it is a (named or unnamed) pointer to a
/// (named or unnamed) struct and returns its base. Otherwise it returns typ.   
pub fn deref_struct_ptr(t: TypeKey, objs: &TCObjects) -> TypeKey {
    let ut = typ::underlying_type(t, objs);
    match &objs.types[ut] {
        typ::Type::Pointer(detail) => {
            let but = typ::underlying_type(detail.base(), objs);
            match &objs.types[but] {
                typ::Type::Struct(_) => but,
                _ => t,
            }
        }
        _ => t,
    }
}

/// field_index returns the index for the field with matching package and name.
pub fn field_index(
    fields: &Vec<ObjKey>,
    pkg: Option<PackageKey>,
    name: &str,
    objs: &TCObjects,
) -> Option<usize> {
    if name != "_" {
        fields
            .iter()
            .enumerate()
            .find(|(_i, x)| objs.lobjs[**x].same_id(pkg, name, objs))
            .map(|(i, _x)| i)
    } else {
        None
    }
}
/// lookup_method returns the index of and method with matching package and name.
pub fn lookup_method<'a>(
    methods: &'a Vec<ObjKey>,
    pkg: Option<PackageKey>,
    name: &str,
    objs: &TCObjects,
) -> Option<(usize, &'a ObjKey)> {
    if name != "_" {
        methods
            .iter()
            .enumerate()
            .find(|(_i, x)| objs.lobjs[**x].same_id(pkg, name, objs))
    } else {
        None
    }
}

/// missing_method returns None if 't' implements 'intf', otherwise it
/// returns a missing method required by T and whether it is missing or
/// just has the wrong type.
///
/// For non-interface types 't', or if static is set, 't' implements
/// 'intf' if all methods of 'intf' are present in 't'. Otherwise ('t'
/// is an interface and static is not set), missing_method only checks
/// that methods of 'intf' which are also present in 't' have matching
/// types (e.g., for a type assertion x.(T) where x is of
/// interface type 't').
pub fn missing_method(
    t: TypeKey,
    intf: TypeKey,
    static_: bool,
    objs: &TCObjects,
) -> Option<(ObjKey, bool)> {
    let ival = &objs.types[intf].try_as_interface().unwrap();
    if ival.is_empty() {
        return None;
    }
    let tval = objs.types[t].underlying_val(objs);
    if let Some(detail) = tval.try_as_interface() {
        for fkey in ival.all_methods().as_ref().unwrap().iter() {
            let fval = &objs.lobjs[*fkey];
            if let Some((_i, f)) = lookup_method(
                detail.all_methods().as_ref().unwrap(),
                fval.pkg(),
                fval.name(),
                objs,
            ) {
                if !typ::identical_option(fval.typ(), objs.lobjs[*f].typ(), objs) {
                    return Some((*fkey, true));
                }
            } else if static_ {
                return Some((*fkey, false));
            }
        }
        return None;
    }
    // A concrete type implements 'intf' if it implements all methods of 'intf'.
    for fkey in ival.all_methods().as_ref().unwrap().iter() {
        let fval = &objs.lobjs[*fkey];
        match lookup_field_or_method(t, false, fval.pkg(), fval.name(), objs) {
            LookupResult::Entry(okey, _, _) => {
                let result_obj = &objs.lobjs[okey];
                if !result_obj.entity_type().is_func() {
                    return Some((*fkey, false));
                } else if !typ::identical_option(fval.typ(), result_obj.typ(), objs) {
                    return Some((*fkey, true));
                }
            }
            _ => return Some((*fkey, false)),
        }
    }
    None
}

fn lookup_field_or_method_impl(
    tkey: TypeKey,
    addressable: bool,
    pkg: Option<PackageKey>,
    name: &str,
    objs: &TCObjects,
) -> LookupResult {
    if name == "_" {
        return LookupResult::NotFound;
    }
    let (tkey, is_ptr) = try_deref(tkey, objs);
    if is_ptr && typ::is_interface(tkey, objs) {
        // pointer to interface has no methods
        return LookupResult::NotFound;
    }
    // Start with typ as single entry at shallowest depth.
    let mut current = vec![EmbeddedType::new(tkey, None, is_ptr, false)];
    // indices is lazily initialized
    let mut indices = None;
    let mut target = None;
    let mut indirect = false;
    // Named types that we have seen already, allocated lazily.
    // Used to avoid endless searches in case of recursive types.
    // Since only Named types can be used for recursive types, we
    // only need to track those.
    // (If we ever allow type aliases to construct recursive types,
    // we must use type identity rather than pointer equality for
    // the map key comparison, as we do in consolidate_multiples.)
    // seen is lazily initialized
    let mut seen: Option<HashSet<TypeKey>> = None;
    while !current.is_empty() {
        // embedded types found at current depth
        let mut next = vec![];
        for et in current.iter() {
            let mut tobj = &objs.types[et.typ];
            if let typ::Type::Named(detail) = tobj {
                if seen.is_none() {
                    seen = Some(HashSet::new());
                }
                let seen_mut = seen.as_mut().unwrap();
                if seen_mut.contains(&et.typ) {
                    // We have seen this type before, at a more shallow depth
                    // (note that multiples of this type at the current depth
                    // were consolidated before). The type at that depth shadows
                    // this same type at the current depth, so we can ignore
                    // this one.
                    continue;
                }
                seen_mut.insert(et.typ);
                // look for a matching attached method
                if let Some((i, &okey)) = lookup_method(detail.methods(), pkg, name, objs) {
                    lookup_on_found!(indices, i, &mut target, et, indirect, okey);
                    continue; // we can't have a matching field or interface method
                }
                // continue with underlying type
                tobj = &objs.types[detail.underlying()];
            }
            match tobj {
                typ::Type::Struct(detail) => {
                    for (i, &f) in detail.fields().iter().enumerate() {
                        let fobj = &objs.lobjs[f];
                        if fobj.same_id(pkg, name, objs) {
                            lookup_on_found!(indices, i, &mut target, et, indirect, f);
                            continue; // we can't have a matching field or interface method
                        }
                        // Collect embedded struct fields for searching the next
                        // lower depth, but only if we have not seen a match yet
                        // (if we have a match it is either the desired field or
                        // we have a name collision on the same depth; in either
                        // case we don't need to look further).
                        // Embedded fields are always of the form T or *T where
                        // T is a type name. If e.typ appeared multiple times at
                        // this depth, f.typ appears multiple times at the next
                        // depth.
                        if target.is_none() && fobj.var_embedded() {
                            let (tkey, is_ptr) = try_deref(fobj.typ().unwrap(), objs);
                            match &objs.types[tkey] {
                                typ::Type::Named(_)
                                | typ::Type::Struct(_)
                                | typ::Type::Interface(_) => next.push(EmbeddedType::new(
                                    tkey,
                                    concat_vec(indices.clone(), i),
                                    et.indirect || is_ptr,
                                    et.multiples,
                                )),
                                _ => {}
                            }
                        }
                    }
                }
                typ::Type::Interface(detail) => {
                    let all = detail.all_methods();
                    if let Some((i, &okey)) = lookup_method(all.as_ref().unwrap(), pkg, name, objs)
                    {
                        lookup_on_found!(indices, i, &mut target, et, indirect, okey);
                    }
                }
                _ => {}
            }
        }
        if let Some(okey) = target {
            // found a potential match
            // spec: "A method call x.m() is valid if the method set of (the type of) x
            //        contains m and the argument list can be assigned to the parameter
            //        list of m. If x is addressable and &x's method set contains m, x.m()
            //        is shorthand for (&x).m()".
            let lobj = &objs.lobjs[okey];
            if lobj.entity_type().is_func() && ptr_recv(lobj, objs) && !indirect && !addressable {
                return LookupResult::BadMethodReceiver;
            }
            return LookupResult::Entry(okey, indices.unwrap(), indirect);
        }
        current = consolidate_multiples(next, objs);
    }
    LookupResult::NotFound
}

/// ptr_recv returns true if the receiver is of the form *T.
fn ptr_recv(lo: &obj::LangObj, objs: &TCObjects) -> bool {
    // If a method's receiver type is set, use that as the source of truth for the receiver.
    // Caution: Checker.func_decl (decl.rs) marks a function by setting its type to an empty
    // signature. We may reach here before the signature is fully set up: we must explicitly
    // check if the receiver is set (we cannot just look for non-None lo.typ).
    if lo.typ().is_none() {
        return false;
    }
    if let Some(sig) = objs.types[lo.typ().unwrap()].try_as_signature() {
        if let Some(re) = sig.recv() {
            let t = objs.lobjs[*re].typ().unwrap();
            let (_, is_ptr) = try_deref(t, objs);
            return is_ptr;
        }
    }
    // If a method's type is not set it may be a method/function that is:
    // 1) client-supplied (via NewFunc with no signature), or
    // 2) internally created but not yet type-checked.
    // For case 1) we can't do anything; the client must know what they are doing.
    // For case 2) we can use the information gathered by the resolver.
    lo.entity_type().func_has_ptr_recv()
}

/// concat_vec returns the result of concatenating list and i.
fn concat_vec(list: Option<Vec<usize>>, i: usize) -> Option<Vec<usize>> {
    if list.is_none() {
        return Some(vec![i]);
    }
    let mut result = list.unwrap();
    result.push(i);
    Some(result)
}

#[derive(Debug)]
struct EmbeddedType {
    typ: TypeKey,
    indices: Option<Vec<usize>>, // lazy init
    indirect: bool,
    multiples: bool,
}

impl EmbeddedType {
    fn new(
        typ: TypeKey,
        indices: Option<Vec<usize>>,
        indirect: bool,
        multiples: bool,
    ) -> EmbeddedType {
        EmbeddedType {
            typ: typ,
            indices: indices,
            indirect: indirect,
            multiples: multiples,
        }
    }
}

/// consolidate_multiples collects multiple list entries with the same type
/// into a single entry marked as containing multiples.
/// it consumes the 'list' and returns a new one
fn consolidate_multiples(list: Vec<EmbeddedType>, objs: &TCObjects) -> Vec<EmbeddedType> {
    let mut result = Vec::with_capacity(list.len());
    if list.len() == 0 {
        return result;
    }
    // lookup finds the identical 'typ' in 'map', returns the value in HashMap
    let lookup = |map: &HashMap<TypeKey, usize>, typ: TypeKey| {
        if let Some(i) = map.get(&typ) {
            Some(*i)
        } else {
            map.iter()
                .find(|(k, _i)| typ::identical(**k, typ, objs))
                .map(|(_k, i)| *i)
        }
    };
    let mut map = HashMap::new();
    for et in list.into_iter() {
        if let Some(i) = lookup(&map, et.typ) {
            result[i].multiples = true;
        } else {
            map.insert(et.typ, result.len());
            result.push(et);
        }
    }
    result
}

/// FieldCollision represents either a field or a name collision
#[derive(Clone, Debug, Eq, PartialEq)]
enum FieldCollision {
    Var(ObjKey),
    Collision,
}

/// add_to_field_set adds field f to the field set s.
/// If multiples is set, f appears multiple times
/// and is treated as a collision.
fn add_to_field_set(
    set: &mut HashMap<String, FieldCollision>,
    f: &ObjKey,
    multiples: bool,
    objs: &TCObjects,
) {
    let key = objs.lobjs[*f].id(objs);
    if !multiples {
        if set
            .insert(key.to_string(), FieldCollision::Var(*f))
            .is_none()
        {
            // no collision
            return;
        }
    }
    set.insert(key.to_string(), FieldCollision::Collision);
}

/// MethodCollision represents either a selection or a name collision
#[derive(Clone, Debug)]
enum MethodCollision {
    Method(Selection),
    Collision,
}

// add_to_method_set adds all functions in list to the method set s.
// If multiples is set, every function in list appears multiple times
// and is treated as a collision.
fn add_to_method_set(
    set: &mut HashMap<String, MethodCollision>,
    list: &Vec<ObjKey>,
    indices: &Vec<usize>,
    indirect: bool,
    multiples: bool,
    objs: &TCObjects,
) {
    for (i, okey) in list.iter().enumerate() {
        let mobj = &objs.lobjs[*okey];
        let key = mobj.id(objs).to_string();
        if !multiples {
            // see the original comment of the Go source in case of a problem
            if !set.contains_key(&key) && (indirect || !ptr_recv(mobj, objs)) {
                set.insert(
                    key,
                    MethodCollision::Method(Selection::new(
                        SelectionKind::MethodVal,
                        None,
                        *okey,
                        concat_vec(Some(indices.clone()), i).unwrap(),
                        indirect,
                        objs,
                    )),
                );
                continue;
            }
        }
        set.insert(key, MethodCollision::Collision);
    }
}