Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 7 additions & 15 deletions src/sema/check/explain_relation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

use super::explain::Line;
use super::explain::NOWHERE;
use super::relate::{REC_BOTH, Relater, Relation, STACK_DEPTH_OVERFLOW, STATE_NONE, Ternary};
use super::relate::{REC_BOTH, Relater, Relation, STATE_NONE, Ternary};
use super::related::Place;
use super::*;
use std::rc::Rc;
Expand Down Expand Up @@ -325,7 +325,7 @@ impl<'p, 's> Checker<'p, 's> {
is_related
}

/// `isTypeRelatedTo`. `Err(code)`: the comparison overflowed (`r.overflow`); `code` is 2859 (complexity) or 2321 (stack depth).
/// `isTypeRelatedTo`. `Err(2859)`: the comparison exceeded the complexity limit.
/// `is_trial`: tsgo has no comparison at this point, see `Relater::caches_failures`.
pub(super) fn try_is_type_related_to(
&mut self,
Expand All @@ -334,23 +334,15 @@ impl<'p, 's> Checker<'p, 's> {
relation: Relation,
is_trial: bool,
) -> Result<bool, u32> {
self.relation_too_deep = false;
let too_complex_before = std::mem::replace(&mut self.relation_too_complex, false);
self.is_trial_comparison = is_trial;
let is_related = self.related(source, target, relation);
self.is_trial_comparison = false;
let is_too_complex = std::mem::replace(&mut self.relation_too_complex, too_complex_before);
let mut is_too_deep = self.relation_too_deep;
// "such that we don't attempt the overflowing operation again"
if !is_related && !is_too_deep && !is_trial {
let (key, _) = self.relation_key(source, target, relation, STATE_NONE, false);
let entry = self.p.relations.get(&self.task, &key);
is_too_deep = entry.is_some_and(|entry| entry & STACK_DEPTH_OVERFLOW != 0);
}
match () {
_ if is_too_complex => Err(2859),
_ if is_too_deep => Err(2321),
_ => Ok(is_related),
if is_too_complex {
Err(2859)
} else {
Ok(is_related)
}
}

Expand Down Expand Up @@ -1103,7 +1095,7 @@ impl<'p, 's> Checker<'p, 's> {
let gives_way = !is_conversion_or_interface_implementation_message(message);
let is_already_reported = match r.get_chain_message(0) {
Some(2353 | 2561) => true,
Some(2859 | 2321 | 4104) => r.chain_args_match(&names),
Some(2859 | 4104) => r.chain_args_match(&names),
Some(2741) => gives_way && r.chain_args_match(&[None, names[0], names[1]]),
Some(2740 | 2739) => gives_way && r.chain_args_match(&names),
_ => false,
Expand Down
21 changes: 21 additions & 0 deletions src/sema/check/flow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3430,6 +3430,9 @@ impl<'p, 's> Checker<'p, 's> {
return TypeId::OBJECT;
}
}
if !loose && let Some(regular) = self.regular_uniform_union_member(ty, value_ty) {
return regular;
}
// `isCoercibleUnderDoubleEquals`
let coerces = loose
&& (matches!(value_ty, TypeId::NUMBER | TypeId::STRING)
Expand All @@ -3444,6 +3447,12 @@ impl<'p, 's> Checker<'p, 's> {
return self.replace_primitives_with_literals(kept, as_written);
}
if self.is_unit(value_ty) {
if self.is_uniform_union(ty) {
let filtered = self.remove_type(ty, value_ty);
if filtered != ty {
return filtered;
}
}
let is_plain = Self::is_literal_outside_enum(self.flags(value_ty));
return self.filter(ty, |c, m| {
if is_plain && Self::is_literal_outside_enum(c.flags(m)) {
Expand All @@ -3455,6 +3464,16 @@ impl<'p, 's> Checker<'p, 's> {
ty
}

fn regular_uniform_union_member(&mut self, ty: TypeId, value: TypeId) -> Option<TypeId> {
if self.has_primitive_flag(value) && self.is_uniform_union(ty) {
let regular = self.regular(value);
if self.union_contains_type(ty, regular, false) {
return Some(regular);
}
}
None
}

/// FOR SPEED: a string, number or bigint literal type that is not a member of an enum. No rule
/// of `isSimpleTypeRelatedTo` relates two of them, so they are comparable only if their regular
/// types are the same. `x.kind !== "a"` tests every member of the type of `x.kind`.
Expand Down Expand Up @@ -4470,6 +4489,8 @@ impl<'p, 's> Checker<'p, 's> {
let discriminant = self.union(clause_types);
let case_type = if discriminant.is_never() {
TypeId::NEVER
} else if let Some(regular) = self.regular_uniform_union_member(ty, discriminant) {
regular
} else {
let kept = self.filter(ty, |c, m| c.are_comparable(discriminant, m));
self.replace_primitives_with_literals(kept, discriminant)
Expand Down
3 changes: 0 additions & 3 deletions src/sema/check/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -732,7 +732,6 @@ impl<'s> Program<'s> {
inherited_names: [0; 4],
cond_distributive_memo: Default::default(),
relation_too_complex: false,
relation_too_deep: false,
current_source_element: None,
is_type_checked: false,
is_emitting: false,
Expand Down Expand Up @@ -1411,8 +1410,6 @@ pub struct Checker<'p, 's> {
cond_distributive_memo: FxHashMap<TypeId, Option<TypeId>>,
/// Set when a comparison exhausts `Relater::relation_count` (2859). The caller clears it before comparing.
pub(super) relation_too_complex: bool,
/// Whether the last `checkTypeRelatedToEx` reached 100 nested comparisons (2321).
pub(super) relation_too_deep: bool,
/// `c.currentNode`, as `checkSourceElement` and `checkDeferredNode` set it. See `current_node`.
pub(super) current_source_element: Option<CurrentNode>,
/// `NodeCheckFlagsTypeChecked` of `task.file`: `check_file` has finished it.
Expand Down
107 changes: 43 additions & 64 deletions src/sema/check/relate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,6 @@ pub(super) const FAILED: u8 = 2;
const REPORTS_UNMEASURABLE: u8 = 8;
const REPORTS_UNRELIABLE: u8 = 16;
pub(super) const COMPLEXITY_OVERFLOW: u8 = 32;
pub(super) const STACK_DEPTH_OVERFLOW: u8 = 64;
const OVERFLOW: u8 = COMPLEXITY_OVERFLOW | STACK_DEPTH_OVERFLOW;

// VarianceFlags
pub(super) const INVARIANT: u8 = 0;
Expand Down Expand Up @@ -229,8 +227,6 @@ pub(super) struct Relater {
pub(super) overflow: bool,
/// A cached `COMPLEXITY_OVERFLOW` entry answered one of the comparisons.
hit_cached_overflow: bool,
/// `len(r.sourceStack)` or `len(r.targetStack)` reached 100, which is why `overflow` is set.
is_too_deep: bool,
/// `related` has already applied its own rules to `top_source` and `top_target`: no simple rule
/// relates them, and they are not an object type and a primitive. Consumed by the first
/// comparison.
Expand Down Expand Up @@ -275,7 +271,6 @@ impl Relater {
expanding: 0,
overflow: false,
hit_cached_overflow: false,
is_too_deep: false,
is_from_related: false,
top_key: None,
relation_count: 2_000_000,
Expand Down Expand Up @@ -1079,10 +1074,9 @@ impl<'p, 's> Checker<'p, 's> {
r.caches_failures = is_trial;
let result = self.is_related_to_ex::<false>(&mut r, source, target, REC_BOTH, STATE_NONE);
let overflow = r.overflow;
// `relationCount <= 0`. Running out of nesting depth or native stack is not a complexity overflow.
// `relationCount <= 0`. Running out of native stack is not a complexity overflow.
let is_too_complex = overflow && r.relation_count <= 0 && self.cycles == r.cycles;
let hit_cached_overflow = r.hit_cached_overflow;
let is_too_deep = overflow && std::mem::take(&mut r.is_too_deep) && !is_trial;
r.is_from_related = false;
r.top_key = None;
r.maybe_keys.clear();
Expand All @@ -1098,17 +1092,12 @@ impl<'p, 's> Checker<'p, 's> {
}
self.free_relaters.push(r);
self.end_comparison(is_outermost);
self.relation_too_deep = is_too_deep;
if is_too_complex || is_too_deep {
let (code, kind) = match is_too_complex {
true => (2859, COMPLEXITY_OVERFLOW),
false => (2321, STACK_DEPTH_OVERFLOW),
};
self.set_relation_overflow(source, target, relation, kind);
if is_too_complex {
self.set_relation_overflow(source, target, relation, COMPLEXITY_OVERFLOW);
self.relation_too_complex |= is_too_complex;
if !is_trial {
let types = [Arg::Type(source), Arg::Type(target)];
self.error_about_comparison_at_current_node(code, &types);
self.error_about_comparison_at_current_node(2859, &types);
}
} else if hit_cached_overflow && !overflow && !result.holds() {
// `reportRelationError`: the overflow replaces the relation error only if it was recorded for `source` and `target`.
Expand Down Expand Up @@ -3750,16 +3739,11 @@ impl<'p, 's> Checker<'p, 's> {
};
if let Some(entry) = cached
// A cached failure is recomputed to produce its error elaboration.
&& !(REPORT && entry & FAILED != 0 && entry & OVERFLOW == 0)
&& !(REPORT && entry & FAILED != 0 && entry & COMPLEXITY_OVERFLOW == 0)
{
self.reliability |= entry & (REPORTS_UNMEASURABLE | REPORTS_UNRELIABLE);
if REPORT && entry & OVERFLOW != 0 {
let code = if entry & COMPLEXITY_OVERFLOW != 0 {
2859
} else {
2321
};
self.report_error(r, code, &[Arg::Type(source), Arg::Type(target)]);
if REPORT && entry & COMPLEXITY_OVERFLOW != 0 {
self.report_error(r, 2859, &[Arg::Type(source), Arg::Type(target)]);
}
if entry & COMPLEXITY_OVERFLOW != 0 {
// The comparison was aborted when it ran.
Expand Down Expand Up @@ -3787,9 +3771,12 @@ impl<'p, 's> Checker<'p, 's> {
return Ternary::MAYBE;
}
}
let is_too_deep = r.source_stack.len() == 100 || r.target_stack.len() == 100;
if is_too_deep || self.is_stack_low() {
r.is_too_deep |= is_too_deep;
if r.source_stack.len() == 100 || r.target_stack.len() == 100 {
// The relation depth limit is indeterminate, not proof that the types differ.
r.maybe_keys_set.remove(&key);
return Ternary::MAYBE;
}
if self.is_stack_low() {
r.maybe_keys_set.remove(&key);
r.overflow = true;
return Ternary::FALSE;
Expand Down Expand Up @@ -3850,7 +3837,7 @@ impl<'p, 's> Checker<'p, 's> {
// A result that is false under assumptions is false without them, and so is one that
// an overflow has cut off. Not so where the native stack ran low, which is no overflow
// in typescript-go.
let is_cut_short = r.overflow && !r.is_too_deep && r.relation_count > 0;
let is_cut_short = r.overflow && r.relation_count > 0;
if r.caches_failures {
if !is_cut_short {
r.failed.insert(key);
Expand All @@ -3877,13 +3864,11 @@ impl<'p, 's> Checker<'p, 's> {
source: TypeId,
target: TypeId,
) -> Option<u32> {
let (code, kind) = match () {
_ if r.relation_count <= 0 => (2859, COMPLEXITY_OVERFLOW),
_ if r.is_too_deep => (2321, STACK_DEPTH_OVERFLOW),
_ => return None,
};
self.set_relation_overflow(source, target, r.relation, kind);
Some(code)
if r.relation_count > 0 {
return None;
}
self.set_relation_overflow(source, target, r.relation, COMPLEXITY_OVERFLOW);
Some(2859)
}

/// `relation.set(id, RelationComparisonResultFailed|kind)` of `checkTypeRelatedToEx`. It
Expand Down Expand Up @@ -3964,13 +3949,11 @@ impl<'p, 's> Checker<'p, 's> {
if stack.len() < max_depth {
return false;
}
let (t, data) = self.mapped_target_with_symbol(t);
if let TypeData::Intersection(parts) = data
&& parts
let (t, data) = self.recursion_identity_target(t);
if let TypeData::Intersection(parts) = data {
return parts
.iter()
.any(|&p| self.is_deeply_nested_type(p, stack, max_depth))
{
return true;
.any(|&p| self.is_deeply_nested_type(p, stack, max_depth));
}
let identity = self.recursion_identity_as(t, data);
let mut count = 0;
Expand Down Expand Up @@ -4038,11 +4021,15 @@ impl<'p, 's> Checker<'p, 's> {
false
}

/// `recursion_identity`. `NOT_PLAIN` for a mapped type, which uses the type it is applied to,
/// and for an intersection, which uses its members.
/// `recursion_identity`. Mapped types and indexed accesses use their target; intersections use their members.
fn plain_recursion_identity(&self, t: TypeId) -> RecursionId {
let data = self.data(t);
if is_mapped_kind(data) || matches!(data, TypeData::Intersection(_)) {
if is_mapped_kind(data)
|| matches!(
data,
TypeData::Intersection(_) | TypeData::IndexedAccess { .. }
)
{
NOT_PLAIN
} else {
self.recursion_identity_as(t, data)
Expand All @@ -4060,18 +4047,17 @@ impl<'p, 's> Checker<'p, 's> {
}
}

/// `getMappedTargetWithSymbol`: the type a homomorphic mapped type is applied to, through any
/// number of nested ones, provided that type has a symbol. `Id<{ x: .. }>` is then
/// distinguished by the type literal it is applied to. Returned with its `TypeData`.
/// `getRecursionIdentityTarget`: indexed accesses and mapped types share their target's identity.
#[inline]
fn mapped_target_with_symbol(&mut self, t: TypeId) -> (TypeId, &'p TypeData<'p>) {
let data = self.data(t);
if is_mapped_kind(data) {
let t = self.target_with_symbol_of_mapped(t);
(t, self.data(t))
} else {
(t, data)
fn recursion_identity_target(&mut self, mut t: TypeId) -> (TypeId, &'p TypeData<'p>) {
while let TypeData::IndexedAccess { obj, .. } = self.data(t) {
t = *obj;
}
if is_mapped_kind(self.data(t)) {
// This walk cannot return an indexed access; do not restart its circular-alias limit.
t = self.target_with_symbol_of_mapped(t);
}
(t, self.data(t))
}

fn target_with_symbol_of_mapped(&mut self, of: TypeId) -> TypeId {
Expand Down Expand Up @@ -4118,7 +4104,7 @@ impl<'p, 's> Checker<'p, 's> {

/// `hasMatchingRecursionIdentity`
fn has_matching_recursion_identity(&mut self, t: TypeId, identity: RecursionId) -> bool {
let (t, data) = self.mapped_target_with_symbol(t);
let (t, data) = self.recursion_identity_target(t);
match data {
TypeData::Intersection(parts) => parts
.iter()
Expand Down Expand Up @@ -4163,8 +4149,9 @@ impl<'p, 's> Checker<'p, 's> {
}

/// `getRecursionIdentity`
pub(super) fn recursion_identity(&self, t: TypeId) -> RecursionId {
self.recursion_identity_as(t, self.data(t))
pub(super) fn recursion_identity(&mut self, t: TypeId) -> RecursionId {
let (t, data) = self.recursion_identity_target(t);
self.recursion_identity_as(t, data)
}

/// `data`: the `TypeData` of `t`.
Expand Down Expand Up @@ -4214,14 +4201,6 @@ impl<'p, 's> Checker<'p, 's> {
(5, flags.len() as u32, hasher.finish() as u32)
}
TypeData::Cond { file, node, .. } => (2, file.0, node.0),
TypeData::IndexedAccess { obj, .. } => {
// The leftmost object type: `A` in `A[P1][P2][P3]`.
let mut of = *obj;
while let TypeData::IndexedAccess { obj, .. } = self.data(of) {
of = *obj;
}
(0, of.0, 0)
}
_ => (0, t.0, 0),
}
}
Expand Down
5 changes: 4 additions & 1 deletion src/sema/check/shape.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6514,7 +6514,10 @@ impl<'p, 's> Checker<'p, 's> {
// `getResolvedBaseConstraint`: a `StructuredType` is a `ConstrainedType`, so the depth test applies to it, and
// `resolvedBaseConstraint` keeps `noConstraintType` or the type itself from the first visit on.
let is_cut = depth >= 10
&& (depth >= 50 || self.constraint_stack.contains(&self.recursion_identity(ty)));
&& (depth >= 50 || {
let identity = self.recursion_identity(ty);
self.constraint_stack.contains(&identity)
});
let result = if is_cut {
None
} else if is_object_type {
Expand Down
Loading
Loading