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
use std::borrow::Cow;

use stc_ts_errors::DebugExt;
use stc_ts_type_ops::Fix;
use stc_ts_types::{KeywordTypeMetadata, Type, Union, UnionMetadata};
use stc_utils::{cache::Freeze, ext::TypeVecExt};
use swc_common::{Span, Spanned};

use crate::{
    analyzer::{assign::AssignOpts, Analyzer},
    VResult,
};

impl Analyzer<'_, '_> {
    pub(crate) fn narrowed_type_of_assignment(&mut self, span: Span, declared: Type, actual: &Type) -> VResult<Type> {
        declared.assert_valid();
        actual.assert_valid();

        let mut declared = self
            .normalize(Some(span), Cow::Owned(declared), Default::default())
            .context("tried to normalize declared type")?;
        declared.freeze();

        let mut actual = self
            .normalize(Some(span), Cow::Borrowed(actual), Default::default())
            .context("tried to normalize declared type")?;
        actual.freeze();

        if let Type::Union(actual) = actual.normalize() {
            let mut new_types = vec![];
            for actual in &actual.types {
                let ty = self.narrowed_type_of_assignment(span, declared.clone().into_owned(), actual)?;
                new_types.push(ty);
            }

            new_types.dedup_type();

            new_types.retain(|ty| !ty.is_never());
            if new_types.is_empty() {
                return Ok(Type::never(
                    actual.span,
                    KeywordTypeMetadata {
                        common: actual.metadata.common,
                        ..Default::default()
                    },
                ));
            }

            return Ok(Type::Union(Union {
                span: actual.span,
                types: new_types,
                metadata: actual.metadata,
                tracker: Default::default(),
            })
            .fixed());
        }

        let mut declared = declared.into_owned();
        declared.normalize_mut();
        // TODO(kdy1): PERF

        match declared {
            Type::Union(declared) => {
                let mut new_types = vec![];
                for declared in declared.types {
                    let ty = self.narrowed_type_of_assignment(span, declared, &actual)?;
                    new_types.push(ty);
                }

                new_types.dedup_type();

                new_types.retain(|ty| !ty.is_never());
                if new_types.is_empty() {
                    return Ok(Type::never(
                        actual.span(),
                        KeywordTypeMetadata {
                            common: actual.metadata(),
                            ..Default::default()
                        },
                    ));
                }

                Ok(Type::Union(Union {
                    span: actual.span(),
                    types: new_types,
                    metadata: UnionMetadata {
                        common: actual.metadata(),
                        ..Default::default()
                    },
                    tracker: Default::default(),
                })
                .fixed())
            }
            _ => {
                if let Ok(()) = self.assign_with_opts(
                    &mut Default::default(),
                    &declared,
                    &actual,
                    AssignOpts {
                        span,
                        allow_unknown_rhs: Some(true),
                        ..Default::default()
                    },
                ) {
                    return Ok(declared);
                }

                Ok(Type::never(
                    actual.span(),
                    KeywordTypeMetadata {
                        common: actual.metadata(),
                        ..Default::default()
                    },
                ))
            }
        }
    }
}