Skip to content
Merged
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
365 changes: 262 additions & 103 deletions crates/by_transforms/src/transforms/checked_cast.rs

Large diffs are not rendered by default.

914 changes: 753 additions & 161 deletions crates/by_transforms/src/transforms/parametric_is.rs

Large diffs are not rendered by default.

50 changes: 45 additions & 5 deletions crates/by_transforms/src/type_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,15 @@ pub(crate) trait TypeInfo {
rhs: &Expr,
) -> Option<ty_python_semantic::ParametricIsPlan>;

/// [`Self::parametric_is_plan`] for a checked cast's `(value, target)` pair.
/// the same classification engine backs both; only the target's inference
/// position differs (a cast target is a type expression)
fn parametric_cast_plan(
&self,
value: &Expr,
target: &Expr,
) -> Option<ty_python_semantic::ParametricIsPlan>;

/// whether an `is`/`is not` comparison whose rhs is `expr` keeps python
/// identity semantics instead of lowering to `isinstance`: true when
/// `expr` resolves to a plain *value* — an enum member (`Color.RED`, a
Expand Down Expand Up @@ -193,11 +202,13 @@ pub(crate) trait TypeInfo {
/// the runtime check for a type expression used as a checked cast target,
/// or `None` when the type has no faithful runtime test. a user generic
/// whose instances carry `__orig_class__` yields a deep
/// [`SoundnessCheck::Parametric`] (`A[int]`); anything else collapses to a
/// shallow [`SoundnessCheck::Isinstance`] (`list[object]` → `list`,
/// [`CastCheck::Kind`]`(`[`SoundnessCheck::Parametric`]`)` (`A[int]`); a
/// protocol target yields [`CastCheck::Protocol`] (checkable structurally);
/// anything else collapses to a shallow
/// [`SoundnessCheck::Isinstance`] (`list[object]` → `list`,
/// `int | str` → `(int, str)`), since `isinstance(x, list[object])` is
/// itself a runtime error
fn cast_check_plan(&self, type_expr: &Expr) -> Option<SoundnessCheck>;
fn cast_check_plan(&self, type_expr: &Expr) -> Option<CastCheck>;

/// whether a `cast`'s value is already statically the target type, so its
/// runtime check is redundant and the value passes through unchecked. this
Expand All @@ -206,6 +217,12 @@ pub(crate) trait TypeInfo {
/// Sequence[object]`) or a subscripted builtin (`B[int]() cast list[int]`)
fn cast_is_redundant(&self, value: &Expr, target: &Expr) -> bool;

/// whether a checked cast to this target has no faithful runtime residue —
/// a protocol with a method member — so it must degrade to an unchecked
/// `typing.cast` rather than an `isinstance` against the protocol, which
/// raises at runtime. a data-member protocol is checkable and returns `false`
fn cast_target_is_unverifiable(&self, type_expr: &Expr) -> bool;

/// the keyword a trailing lambda block is passed with — the name of the
/// callee's last declared parameter. `None` means the lambda is appended
/// as a positional argument instead (unknown callee signature, or a
Expand Down Expand Up @@ -277,6 +294,10 @@ pub(crate) trait TypeInfo {
/// re-export of the ty-side check plan so transforms name a single type
pub(crate) use ty_python_semantic::types::soundness::CheckKind as SoundnessCheck;

/// re-export of the ty-side checked-cast plan (a superset of [`SoundnessCheck`]
/// that also carries protocol-structural and unchecked cases)
pub(crate) use ty_python_semantic::types::soundness::CastCheck;

/// re-export of the ty-side framework role so transforms name a single type
pub(crate) use ty_python_semantic::types::FrameworkRole;

Expand Down Expand Up @@ -332,6 +353,14 @@ impl TypeInfo for SemanticModel<'_> {
SemanticModel::parametric_is_plan(self, lhs, rhs)
}

fn parametric_cast_plan(
&self,
value: &Expr,
target: &Expr,
) -> Option<ty_python_semantic::ParametricIsPlan> {
SemanticModel::parametric_cast_plan(self, value, target)
}

fn is_keeps_identity(&self, expr: &Expr) -> bool {
expr.inferred_type(self).is_some_and(|ty| {
ty_python_semantic::types::basedpython_is_keeps_identity(self.db(), ty)
Expand Down Expand Up @@ -553,9 +582,9 @@ impl TypeInfo for SemanticModel<'_> {
)
}

fn cast_check_plan(&self, type_expr: &Expr) -> Option<SoundnessCheck> {
fn cast_check_plan(&self, type_expr: &Expr) -> Option<CastCheck> {
let ty = type_expr.inferred_type(self)?;
ty_python_semantic::types::soundness::runtime_check_plan(self.db(), self.file(), ty)
ty_python_semantic::types::soundness::cast_check_plan(self.db(), self.file(), ty)
}

fn cast_is_redundant(&self, value: &Expr, target: &Expr) -> bool {
Expand All @@ -567,6 +596,17 @@ impl TypeInfo for SemanticModel<'_> {
ty_python_semantic::types::soundness::cast_is_redundant(self.db(), value_ty, target_ty)
}

fn cast_target_is_unverifiable(&self, type_expr: &Expr) -> bool {
let Some(ty) = type_expr.inferred_type(self) else {
return false;
};
ty_python_semantic::types::soundness::cast_target_is_unverifiable_protocol(
self.db(),
self.file(),
ty,
)
}

fn trailing_lambda_keyword(&self, callee: &Expr) -> Option<String> {
SemanticModel::trailing_lambda_keyword(self, callee)
}
Expand Down
Loading
Loading