tsz-solver 0.1.8

TypeScript type solver for the tsz compiler
Documentation
use crate::TypeDatabase;
use crate::types::{IntrinsicKind, TypeId};

pub enum ApparentMemberKind {
    Value(TypeId),
    Method(TypeId),
}

pub struct ApparentMember {
    pub name: &'static str,
    pub kind: ApparentMemberKind,
}

const STRING_METHODS_RETURN_STRING: &[&str] = &[
    "anchor",
    "at",
    "big",
    "blink",
    "bold",
    "charAt",
    "concat",
    "fixed",
    "fontcolor",
    "fontsize",
    "italics",
    "link",
    "normalize",
    "padEnd",
    "padStart",
    "repeat",
    "replace",
    "replaceAll",
    "slice",
    "small",
    "strike",
    "sub",
    "substr",
    "substring",
    "sup",
    "toLocaleLowerCase",
    "toLocaleUpperCase",
    "toLowerCase",
    "toString",
    "toUpperCase",
    "trim",
    "trimEnd",
    "trimLeft",
    "trimRight",
    "trimStart",
    "toWellFormed",
    "valueOf",
];
const STRING_METHODS_RETURN_NUMBER: &[&str] = &[
    "charCodeAt",
    "codePointAt",
    "indexOf",
    "lastIndexOf",
    "localeCompare",
    "search",
];
const STRING_METHODS_RETURN_BOOLEAN: &[&str] =
    &["endsWith", "includes", "isWellFormed", "startsWith"];
const STRING_METHODS_RETURN_ANY: &[&str] = &["match", "matchAll"];
const STRING_METHODS_RETURN_STRING_ARRAY: &[&str] = &["split"];

const NUMBER_METHODS_RETURN_STRING: &[&str] = &[
    "toExponential",
    "toFixed",
    "toLocaleString",
    "toPrecision",
    "toString",
];

const BOOLEAN_METHODS_RETURN_STRING: &[&str] = &["toLocaleString", "toString"];

const BIGINT_METHODS_RETURN_STRING: &[&str] = &["toLocaleString", "toString"];

const OBJECT_METHODS_RETURN_BOOLEAN: &[&str] =
    &["hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable"];
const OBJECT_METHODS_RETURN_STRING: &[&str] = &["toString"];
const OBJECT_METHODS_RETURN_ANY: &[&str] = &["valueOf"];

fn is_member(name: &str, list: &[&str]) -> bool {
    list.contains(&name)
}

fn object_member_kind(name: &str, include_to_locale: bool) -> Option<ApparentMemberKind> {
    if name == "constructor" {
        return Some(ApparentMemberKind::Value(TypeId::FUNCTION));
    }
    if name == "toString" {
        return Some(ApparentMemberKind::Method(TypeId::STRING));
    }
    if name == "valueOf" {
        return Some(ApparentMemberKind::Method(TypeId::ANY));
    }
    if is_member(name, OBJECT_METHODS_RETURN_BOOLEAN) {
        return Some(ApparentMemberKind::Method(TypeId::BOOLEAN));
    }
    if is_member(name, OBJECT_METHODS_RETURN_STRING) {
        return Some(ApparentMemberKind::Method(TypeId::STRING));
    }
    if is_member(name, OBJECT_METHODS_RETURN_ANY) {
        return Some(ApparentMemberKind::Method(TypeId::ANY));
    }
    if include_to_locale && name == "toLocaleString" {
        return Some(ApparentMemberKind::Method(TypeId::STRING));
    }
    None
}

fn push_object_members(members: &mut Vec<ApparentMember>, include_to_locale: bool) {
    members.push(ApparentMember {
        name: "constructor",
        kind: ApparentMemberKind::Value(TypeId::ANY),
    });
    members.push(ApparentMember {
        name: "toString",
        kind: ApparentMemberKind::Method(TypeId::STRING),
    });
    members.push(ApparentMember {
        name: "valueOf",
        kind: ApparentMemberKind::Method(TypeId::ANY),
    });
    for &name in OBJECT_METHODS_RETURN_BOOLEAN {
        members.push(ApparentMember {
            name,
            kind: ApparentMemberKind::Method(TypeId::BOOLEAN),
        });
    }
    for &name in OBJECT_METHODS_RETURN_STRING {
        members.push(ApparentMember {
            name,
            kind: ApparentMemberKind::Method(TypeId::STRING),
        });
    }
    for &name in OBJECT_METHODS_RETURN_ANY {
        members.push(ApparentMember {
            name,
            kind: ApparentMemberKind::Method(TypeId::ANY),
        });
    }
    if include_to_locale {
        members.push(ApparentMember {
            name: "toLocaleString",
            kind: ApparentMemberKind::Method(TypeId::STRING),
        });
    }
}

pub fn apparent_object_member_kind(name: &str) -> Option<ApparentMemberKind> {
    object_member_kind(name, true)
}

pub fn apparent_primitive_member_kind(
    interner: &dyn TypeDatabase,
    kind: IntrinsicKind,
    name: &str,
) -> Option<ApparentMemberKind> {
    match kind {
        IntrinsicKind::String => {
            if name == "length" {
                return Some(ApparentMemberKind::Value(TypeId::NUMBER));
            }
            if is_member(name, STRING_METHODS_RETURN_STRING) {
                return Some(ApparentMemberKind::Method(TypeId::STRING));
            }
            if is_member(name, STRING_METHODS_RETURN_NUMBER) {
                return Some(ApparentMemberKind::Method(TypeId::NUMBER));
            }
            if is_member(name, STRING_METHODS_RETURN_BOOLEAN) {
                return Some(ApparentMemberKind::Method(TypeId::BOOLEAN));
            }
            if is_member(name, STRING_METHODS_RETURN_ANY) {
                return Some(ApparentMemberKind::Method(TypeId::ANY));
            }
            if is_member(name, STRING_METHODS_RETURN_STRING_ARRAY) {
                let string_array = interner.array(TypeId::STRING);
                return Some(ApparentMemberKind::Method(string_array));
            }
            object_member_kind(name, true)
        }
        IntrinsicKind::Number => {
            if is_member(name, NUMBER_METHODS_RETURN_STRING) {
                return Some(ApparentMemberKind::Method(TypeId::STRING));
            }
            if name == "valueOf" {
                return Some(ApparentMemberKind::Method(TypeId::NUMBER));
            }
            object_member_kind(name, false)
        }
        IntrinsicKind::Boolean => {
            if is_member(name, BOOLEAN_METHODS_RETURN_STRING) {
                return Some(ApparentMemberKind::Method(TypeId::STRING));
            }
            if name == "valueOf" {
                return Some(ApparentMemberKind::Method(TypeId::BOOLEAN));
            }
            object_member_kind(name, false)
        }
        IntrinsicKind::Bigint => {
            if is_member(name, BIGINT_METHODS_RETURN_STRING) {
                return Some(ApparentMemberKind::Method(TypeId::STRING));
            }
            if name == "valueOf" {
                return Some(ApparentMemberKind::Method(TypeId::BIGINT));
            }
            object_member_kind(name, false)
        }
        IntrinsicKind::Symbol => {
            if name == "description" {
                let description = interner.union2(TypeId::STRING, TypeId::UNDEFINED);
                return Some(ApparentMemberKind::Value(description));
            }
            if name == "toString" {
                return Some(ApparentMemberKind::Method(TypeId::STRING));
            }
            if name == "valueOf" {
                return Some(ApparentMemberKind::Method(TypeId::SYMBOL));
            }
            object_member_kind(name, true)
        }
        IntrinsicKind::Object => object_member_kind(name, true),
        _ => None,
    }
}

pub fn apparent_primitive_members(
    interner: &dyn TypeDatabase,
    kind: IntrinsicKind,
) -> Vec<ApparentMember> {
    let mut members = Vec::new();

    match kind {
        IntrinsicKind::String => {
            members.push(ApparentMember {
                name: "length",
                kind: ApparentMemberKind::Value(TypeId::NUMBER),
            });
            for &name in STRING_METHODS_RETURN_STRING {
                members.push(ApparentMember {
                    name,
                    kind: ApparentMemberKind::Method(TypeId::STRING),
                });
            }
            for &name in STRING_METHODS_RETURN_NUMBER {
                members.push(ApparentMember {
                    name,
                    kind: ApparentMemberKind::Method(TypeId::NUMBER),
                });
            }
            for &name in STRING_METHODS_RETURN_BOOLEAN {
                members.push(ApparentMember {
                    name,
                    kind: ApparentMemberKind::Method(TypeId::BOOLEAN),
                });
            }
            for &name in STRING_METHODS_RETURN_ANY {
                members.push(ApparentMember {
                    name,
                    kind: ApparentMemberKind::Method(TypeId::ANY),
                });
            }
            let string_array = interner.array(TypeId::STRING);
            for &name in STRING_METHODS_RETURN_STRING_ARRAY {
                members.push(ApparentMember {
                    name,
                    kind: ApparentMemberKind::Method(string_array),
                });
            }
            push_object_members(&mut members, true);
        }
        IntrinsicKind::Number => {
            for &name in NUMBER_METHODS_RETURN_STRING {
                members.push(ApparentMember {
                    name,
                    kind: ApparentMemberKind::Method(TypeId::STRING),
                });
            }
            members.push(ApparentMember {
                name: "valueOf",
                kind: ApparentMemberKind::Method(TypeId::NUMBER),
            });
            push_object_members(&mut members, false);
        }
        IntrinsicKind::Boolean => {
            for &name in BOOLEAN_METHODS_RETURN_STRING {
                members.push(ApparentMember {
                    name,
                    kind: ApparentMemberKind::Method(TypeId::STRING),
                });
            }
            members.push(ApparentMember {
                name: "valueOf",
                kind: ApparentMemberKind::Method(TypeId::BOOLEAN),
            });
            push_object_members(&mut members, false);
        }
        IntrinsicKind::Bigint => {
            for &name in BIGINT_METHODS_RETURN_STRING {
                members.push(ApparentMember {
                    name,
                    kind: ApparentMemberKind::Method(TypeId::STRING),
                });
            }
            members.push(ApparentMember {
                name: "valueOf",
                kind: ApparentMemberKind::Method(TypeId::BIGINT),
            });
            push_object_members(&mut members, false);
        }
        IntrinsicKind::Symbol => {
            let description = interner.union2(TypeId::STRING, TypeId::UNDEFINED);
            members.push(ApparentMember {
                name: "description",
                kind: ApparentMemberKind::Value(description),
            });
            members.push(ApparentMember {
                name: "toString",
                kind: ApparentMemberKind::Method(TypeId::STRING),
            });
            members.push(ApparentMember {
                name: "valueOf",
                kind: ApparentMemberKind::Method(TypeId::SYMBOL),
            });
            push_object_members(&mut members, true);
        }
        IntrinsicKind::Object => {
            push_object_members(&mut members, true);
        }
        _ => {}
    }

    members
}