Skip to content

Commit

Permalink
Introduce TyContext
Browse files Browse the repository at this point in the history
  • Loading branch information
MichaReiser committed Dec 13, 2024
1 parent dfd7f38 commit d9a29da
Show file tree
Hide file tree
Showing 9 changed files with 795 additions and 721 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion crates/red_knot_python_semantic/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ bitflags = { workspace = true }
camino = { workspace = true }
compact_str = { workspace = true }
countme = { workspace = true }
drop_bomb = { workspace = true }
indexmap = { workspace = true }
itertools = { workspace = true }
ordermap = { workspace = true }
Expand Down Expand Up @@ -58,4 +59,3 @@ serde = ["ruff_db/serde", "dep:serde"]

[lints]
workspace = true

10 changes: 6 additions & 4 deletions crates/red_knot_python_semantic/src/types.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use std::hash::Hash;

use context::TyContext;
use diagnostic::{report_not_iterable, report_not_iterable_possibly_unbound};
use indexmap::IndexSet;
use itertools::Itertools;
use ruff_db::diagnostic::Severity;
Expand Down Expand Up @@ -27,13 +29,13 @@ use crate::stdlib::{
};
use crate::symbol::{Boundness, Symbol};
use crate::types::call::{CallDunderResult, CallOutcome};
use crate::types::diagnostic::TypeCheckDiagnosticsBuilder;
use crate::types::mro::{ClassBase, Mro, MroError, MroIterator};
use crate::types::narrow::narrowing_constraint;
use crate::{Db, FxOrderSet, Module, Program, PythonVersion};

mod builder;
mod call;
mod context;
mod diagnostic;
mod display;
mod infer;
Expand Down Expand Up @@ -2487,20 +2489,20 @@ enum IterationOutcome<'db> {
impl<'db> IterationOutcome<'db> {
fn unwrap_with_diagnostic(
self,
context: &TyContext<'db>,
iterable_node: ast::AnyNodeRef,
diagnostics: &mut TypeCheckDiagnosticsBuilder<'db>,
) -> Type<'db> {
match self {
Self::Iterable { element_ty } => element_ty,
Self::NotIterable { not_iterable_ty } => {
diagnostics.add_not_iterable(iterable_node, not_iterable_ty);
report_not_iterable(context, iterable_node, not_iterable_ty);
Type::Unknown
}
Self::PossiblyUnboundDunderIter {
iterable_ty,
element_ty,
} => {
diagnostics.add_not_iterable_possibly_unbound(iterable_node, iterable_ty);
report_not_iterable_possibly_unbound(context, iterable_node, iterable_ty);
element_ty
}
}
Expand Down
50 changes: 25 additions & 25 deletions crates/red_knot_python_semantic/src/types/call.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use super::diagnostic::{TypeCheckDiagnosticsBuilder, CALL_NON_CALLABLE};
use super::{Severity, Type, TypeArrayDisplay, UnionBuilder};
use super::diagnostic::CALL_NON_CALLABLE;
use super::{Severity, TyContext, Type, TypeArrayDisplay, UnionBuilder};
use crate::Db;
use ruff_db::diagnostic::DiagnosticId;
use ruff_python_ast as ast;
Expand Down Expand Up @@ -86,24 +86,23 @@ impl<'db> CallOutcome<'db> {
}

/// Get the return type of the call, emitting default diagnostics if needed.
pub(super) fn unwrap_with_diagnostic<'a>(
pub(super) fn unwrap_with_diagnostic(
&self,
db: &'db dyn Db,
context: &TyContext<'db>,
node: ast::AnyNodeRef,
diagnostics: &'a mut TypeCheckDiagnosticsBuilder<'db>,
) -> Type<'db> {
match self.return_ty_result(db, node, diagnostics) {
match self.return_ty_result(context, node) {
Ok(return_ty) => return_ty,
Err(NotCallableError::Type {
not_callable_ty,
return_ty,
}) => {
diagnostics.add_lint(
context.report_lint(
&CALL_NON_CALLABLE,
node,
format_args!(
"Object of type `{}` is not callable",
not_callable_ty.display(db)
not_callable_ty.display(context.db())
),
);
return_ty
Expand All @@ -113,13 +112,13 @@ impl<'db> CallOutcome<'db> {
called_ty,
return_ty,
}) => {
diagnostics.add_lint(
context.report_lint(
&CALL_NON_CALLABLE,
node,
format_args!(
"Object of type `{}` is not callable (due to union element `{}`)",
called_ty.display(db),
not_callable_ty.display(db),
called_ty.display(context.db()),
not_callable_ty.display(context.db()),
),
);
return_ty
Expand All @@ -129,13 +128,13 @@ impl<'db> CallOutcome<'db> {
called_ty,
return_ty,
}) => {
diagnostics.add_lint(
context.report_lint(
&CALL_NON_CALLABLE,
node,
format_args!(
"Object of type `{}` is not callable (due to union elements {})",
called_ty.display(db),
not_callable_tys.display(db),
called_ty.display(context.db()),
not_callable_tys.display(context.db()),
),
);
return_ty
Expand All @@ -144,12 +143,12 @@ impl<'db> CallOutcome<'db> {
callable_ty: called_ty,
return_ty,
}) => {
diagnostics.add_lint(
context.report_lint(
&CALL_NON_CALLABLE,
node,
format_args!(
"Object of type `{}` is not callable (possibly unbound `__call__` method)",
called_ty.display(db)
called_ty.display(context.db())
),
);
return_ty
Expand All @@ -158,23 +157,22 @@ impl<'db> CallOutcome<'db> {
}

/// Get the return type of the call as a result.
pub(super) fn return_ty_result<'a>(
pub(super) fn return_ty_result(
&self,
db: &'db dyn Db,
context: &TyContext<'db>,
node: ast::AnyNodeRef,
diagnostics: &'a mut TypeCheckDiagnosticsBuilder<'db>,
) -> Result<Type<'db>, NotCallableError<'db>> {
match self {
Self::Callable { return_ty } => Ok(*return_ty),
Self::RevealType {
return_ty,
revealed_ty,
} => {
diagnostics.add(
context.report_diagnostic(
node,
DiagnosticId::RevealedType,
Severity::Info,
format_args!("Revealed type is `{}`", revealed_ty.display(db)),
format_args!("Revealed type is `{}`", revealed_ty.display(context.db())),
);
Ok(*return_ty)
}
Expand All @@ -187,14 +185,16 @@ impl<'db> CallOutcome<'db> {
call_outcome,
} => Err(NotCallableError::PossiblyUnboundDunderCall {
callable_ty: *called_ty,
return_ty: call_outcome.return_ty(db).unwrap_or(Type::Unknown),
return_ty: call_outcome
.return_ty(context.db())
.unwrap_or(Type::Unknown),
}),
Self::Union {
outcomes,
called_ty,
} => {
let mut not_callable = vec![];
let mut union_builder = UnionBuilder::new(db);
let mut union_builder = UnionBuilder::new(context.db());
let mut revealed = false;
for outcome in outcomes {
let return_ty = match outcome {
Expand All @@ -210,10 +210,10 @@ impl<'db> CallOutcome<'db> {
*return_ty
} else {
revealed = true;
outcome.unwrap_with_diagnostic(db, node, diagnostics)
outcome.unwrap_with_diagnostic(context, node)
}
}
_ => outcome.unwrap_with_diagnostic(db, node, diagnostics),
_ => outcome.unwrap_with_diagnostic(context, node),
};
union_builder = union_builder.add(return_ty);
}
Expand Down
126 changes: 126 additions & 0 deletions crates/red_knot_python_semantic/src/types/context.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
use std::{fmt, ops::Deref};

use drop_bomb::DropBomb;
use ruff_db::{
diagnostic::{DiagnosticId, Severity},
files::File,
};
use ruff_python_ast::AnyNodeRef;
use ruff_text_size::Ranged;

use crate::{
lint::{LintId, LintMetadata},
Db,
};

use super::{TypeCheckDiagnostic, TypeCheckDiagnostics};

/// Context for infering types for a specific file.
pub(crate) struct TyContext<'db> {
db: &'db dyn Db,
file: File,
diagnostics: std::cell::RefCell<TypeCheckDiagnostics>,
defused: DropBomb,
}

impl<'db> TyContext<'db> {
pub(crate) fn new(db: &'db dyn Db, file: File) -> Self {
Self {
db,
file,
diagnostics: std::cell::RefCell::new(TypeCheckDiagnostics::default()),
defused: DropBomb::new("Failed to consume typing context"),
}
}

/// The file for which the types are inferred.
pub(crate) fn file(&self) -> File {
self.file
}

pub(crate) fn db(&self) -> &'db dyn Db {
self.db
}

pub(crate) fn extend<T>(&mut self, other: &T)
where
T: WithDiagnostics,
{
self.diagnostics
.get_mut()
.extend(other.diagnostics().iter().cloned());
}

/// Reports a lint located at `node`.
pub(super) fn report_lint(
&self,
lint: &'static LintMetadata,
node: AnyNodeRef,
message: std::fmt::Arguments,
) {
// Skip over diagnostics if the rule is disabled.
let Some(severity) = self.db.rule_selection().severity(LintId::of(lint)) else {
return;
};

self.report_diagnostic(node, DiagnosticId::Lint(lint.name()), severity, message);
}

/// Adds a new diagnostic.
///
/// The diagnostic does not get added if the rule isn't enabled for this file.
pub(super) fn report_diagnostic(
&self,
node: AnyNodeRef,
id: DiagnosticId,
severity: Severity,
message: std::fmt::Arguments,
) {
if !self.db.is_file_open(self.file) {
return;
}

// TODO: Don't emit the diagnostic if:
// * The enclosing node contains any syntax errors
// * The rule is disabled for this file. We probably want to introduce a new query that
// returns a rule selector for a given file that respects the package's settings,
// any global pragma comments in the file, and any per-file-ignores.
// * Check for suppression comments, bump a counter if the diagnostic is suppressed.

self.diagnostics.borrow_mut().push(TypeCheckDiagnostic {
file: self.file,
id,
message: message.to_string(),
range: node.range(),
severity,
});
}

pub(crate) fn finish(mut self) -> TypeCheckDiagnostics {
self.defused.defuse();
let mut diagnostics = self.diagnostics.into_inner();
diagnostics.shrink_to_fit();
diagnostics
}
}

impl Deref for TyContext<'_> {
type Target = dyn Db;
fn deref(&self) -> &Self::Target {
self.db
}
}

impl fmt::Debug for TyContext<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("TyContext")
.field("file", &self.file)
.field("diagnostics", &self.diagnostics)
.field("defused", &self.defused)
.finish()
}
}

pub(crate) trait WithDiagnostics {
fn diagnostics(&self) -> &TypeCheckDiagnostics;
}
Loading

0 comments on commit d9a29da

Please sign in to comment.