Skip to content

Commit 643bc02

Browse files
committed
Auto merge of rust-lang#13875 - Veykril:private-field-diag, r=Veykril
Diagnose private assoc item accesses
2 parents f31733b + eee7de0 commit 643bc02

File tree

10 files changed

+199
-26
lines changed

10 files changed

+199
-26
lines changed

crates/hir-def/src/expr.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,13 @@ pub(crate) fn dummy_expr_id() -> ExprId {
3636

3737
pub type PatId = Idx<Pat>;
3838

39+
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
40+
pub enum ExprOrPatId {
41+
ExprId(ExprId),
42+
PatId(PatId),
43+
}
44+
stdx::impl_from!(ExprId, PatId for ExprOrPatId);
45+
3946
#[derive(Debug, Clone, Eq, PartialEq)]
4047
pub struct Label {
4148
pub name: Name,

crates/hir-ty/src/infer.rs

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use hir_def::{
2121
body::Body,
2222
builtin_type::{BuiltinInt, BuiltinType, BuiltinUint},
2323
data::{ConstData, StaticData},
24-
expr::{BindingAnnotation, ExprId, PatId},
24+
expr::{BindingAnnotation, ExprId, ExprOrPatId, PatId},
2525
lang_item::LangItemTarget,
2626
layout::Integer,
2727
path::{path, Path},
@@ -34,7 +34,7 @@ use hir_expand::name::{name, Name};
3434
use itertools::Either;
3535
use la_arena::ArenaMap;
3636
use rustc_hash::FxHashMap;
37-
use stdx::{always, impl_from};
37+
use stdx::always;
3838

3939
use crate::{
4040
db::HirDatabase, fold_tys, fold_tys_and_consts, infer::coerce::CoerceMany,
@@ -120,13 +120,6 @@ pub(crate) fn normalize(db: &dyn HirDatabase, owner: DefWithBodyId, ty: Ty) -> T
120120
table.resolve_completely(ty_with_vars)
121121
}
122122

123-
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
124-
enum ExprOrPatId {
125-
ExprId(ExprId),
126-
PatId(PatId),
127-
}
128-
impl_from!(ExprId, PatId for ExprOrPatId);
129-
130123
/// Binding modes inferred for patterns.
131124
/// <https://doc.rust-lang.org/reference/patterns.html#binding-modes>
132125
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
@@ -209,6 +202,7 @@ pub(crate) type InferResult<T> = Result<InferOk<T>, TypeError>;
209202
pub enum InferenceDiagnostic {
210203
NoSuchField { expr: ExprId },
211204
PrivateField { expr: ExprId, field: FieldId },
205+
PrivateAssocItem { id: ExprOrPatId, item: AssocItemId },
212206
BreakOutsideOfLoop { expr: ExprId, is_break: bool },
213207
MismatchedArgCount { call_expr: ExprId, expected: usize, found: usize },
214208
}

crates/hir-ty/src/infer/expr.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1142,20 +1142,26 @@ impl<'a> InferenceContext<'a> {
11421142
let traits_in_scope = self.resolver.traits_in_scope(self.db.upcast());
11431143

11441144
let resolved = method_resolution::lookup_method(
1145-
&canonicalized_receiver.value,
11461145
self.db,
1146+
&canonicalized_receiver.value,
11471147
self.trait_env.clone(),
11481148
&traits_in_scope,
11491149
VisibleFromModule::Filter(self.resolver.module()),
11501150
method_name,
11511151
);
11521152
let (receiver_ty, method_ty, substs) = match resolved {
1153-
Some((adjust, func)) => {
1153+
Some((adjust, func, visible)) => {
11541154
let (ty, adjustments) = adjust.apply(&mut self.table, receiver_ty);
11551155
let generics = generics(self.db.upcast(), func.into());
11561156
let substs = self.substs_for_method_call(generics, generic_args);
11571157
self.write_expr_adj(receiver, adjustments);
11581158
self.write_method_resolution(tgt_expr, func, substs.clone());
1159+
if !visible {
1160+
self.push_diagnostic(InferenceDiagnostic::PrivateAssocItem {
1161+
id: tgt_expr.into(),
1162+
item: func.into(),
1163+
})
1164+
}
11591165
(ty, self.db.value_ty(func.into()), substs)
11601166
}
11611167
None => (

crates/hir-ty/src/infer/path.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ use crate::{
1414
consteval,
1515
method_resolution::{self, VisibleFromModule},
1616
utils::generics,
17-
Interner, Substitution, TraitRefExt, Ty, TyBuilder, TyExt, TyKind, ValueTyDefId,
17+
InferenceDiagnostic, Interner, Substitution, TraitRefExt, Ty, TyBuilder, TyExt, TyKind,
18+
ValueTyDefId,
1819
};
1920

2021
use super::{ExprOrPatId, InferenceContext, TraitRef};
@@ -279,20 +280,23 @@ impl<'a> InferenceContext<'a> {
279280
};
280281

281282
if visible {
282-
Some((def, item, Some(substs)))
283+
Some((def, item, Some(substs), true))
283284
} else {
284285
if not_visible.is_none() {
285-
not_visible = Some((def, item, Some(substs)));
286+
not_visible = Some((def, item, Some(substs), false));
286287
}
287288
None
288289
}
289290
},
290291
);
291292
let res = res.or(not_visible);
292-
if let Some((_, item, Some(ref substs))) = res {
293+
if let Some((_, item, Some(ref substs), visible)) = res {
293294
self.write_assoc_resolution(id, item, substs.clone());
295+
if !visible {
296+
self.push_diagnostic(InferenceDiagnostic::PrivateAssocItem { id, item })
297+
}
294298
}
295-
res.map(|(def, _, substs)| (def, substs))
299+
res.map(|(def, _, substs, _)| (def, substs))
296300
}
297301

298302
fn resolve_enum_variant_on_ty(

crates/hir-ty/src/method_resolution.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -488,13 +488,13 @@ pub fn lang_names_for_bin_op(op: syntax::ast::BinaryOp) -> Option<(Name, Name)>
488488

489489
/// Look up the method with the given name.
490490
pub(crate) fn lookup_method(
491-
ty: &Canonical<Ty>,
492491
db: &dyn HirDatabase,
492+
ty: &Canonical<Ty>,
493493
env: Arc<TraitEnvironment>,
494494
traits_in_scope: &FxHashSet<TraitId>,
495495
visible_from_module: VisibleFromModule,
496496
name: &Name,
497-
) -> Option<(ReceiverAdjustments, FunctionId)> {
497+
) -> Option<(ReceiverAdjustments, FunctionId, bool)> {
498498
let mut not_visible = None;
499499
let res = iterate_method_candidates(
500500
ty,
@@ -505,9 +505,9 @@ pub(crate) fn lookup_method(
505505
Some(name),
506506
LookupMode::MethodCall,
507507
|adjustments, f, visible| match f {
508-
AssocItemId::FunctionId(f) if visible => Some((adjustments, f)),
508+
AssocItemId::FunctionId(f) if visible => Some((adjustments, f, true)),
509509
AssocItemId::FunctionId(f) if not_visible.is_none() => {
510-
not_visible = Some((adjustments, f));
510+
not_visible = Some((adjustments, f, false));
511511
None
512512
}
513513
_ => None,

crates/hir/src/diagnostics.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use hir_def::path::ModPath;
1010
use hir_expand::{name::Name, HirFileId, InFile};
1111
use syntax::{ast, AstPtr, SyntaxNodePtr, TextRange};
1212

13-
use crate::{Field, MacroKind, Type};
13+
use crate::{AssocItem, Field, MacroKind, Type};
1414

1515
macro_rules! diagnostics {
1616
($($diag:ident,)*) => {
@@ -41,6 +41,7 @@ diagnostics![
4141
MissingMatchArms,
4242
MissingUnsafe,
4343
NoSuchField,
44+
PrivateAssocItem,
4445
PrivateField,
4546
ReplaceFilterMapNextWithFindMap,
4647
TypeMismatch,
@@ -122,6 +123,13 @@ pub struct NoSuchField {
122123
pub field: InFile<AstPtr<ast::RecordExprField>>,
123124
}
124125

126+
#[derive(Debug)]
127+
pub struct PrivateAssocItem {
128+
pub expr_or_pat:
129+
InFile<Either<AstPtr<ast::Expr>, Either<AstPtr<ast::Pat>, AstPtr<ast::SelfParam>>>>,
130+
pub item: AssocItem,
131+
}
132+
125133
#[derive(Debug)]
126134
pub struct PrivateField {
127135
pub expr: InFile<AstPtr<ast::Expr>>,

crates/hir/src/lib.rs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ use either::Either;
4141
use hir_def::{
4242
adt::VariantData,
4343
body::{BodyDiagnostic, SyntheticSyntax},
44-
expr::{BindingAnnotation, LabelId, Pat, PatId},
44+
expr::{BindingAnnotation, ExprOrPatId, LabelId, Pat, PatId},
4545
generics::{TypeOrConstParamData, TypeParamProvenance},
4646
item_tree::ItemTreeNode,
4747
lang_item::LangItemTarget,
@@ -85,9 +85,10 @@ pub use crate::{
8585
diagnostics::{
8686
AnyDiagnostic, BreakOutsideOfLoop, InactiveCode, IncorrectCase, InvalidDeriveTarget,
8787
MacroError, MalformedDerive, MismatchedArgCount, MissingFields, MissingMatchArms,
88-
MissingUnsafe, NoSuchField, PrivateField, ReplaceFilterMapNextWithFindMap, TypeMismatch,
89-
UnimplementedBuiltinMacro, UnresolvedExternCrate, UnresolvedImport, UnresolvedMacroCall,
90-
UnresolvedModule, UnresolvedProcMacro,
88+
MissingUnsafe, NoSuchField, PrivateAssocItem, PrivateField,
89+
ReplaceFilterMapNextWithFindMap, TypeMismatch, UnimplementedBuiltinMacro,
90+
UnresolvedExternCrate, UnresolvedImport, UnresolvedMacroCall, UnresolvedModule,
91+
UnresolvedProcMacro,
9192
},
9293
has_source::HasSource,
9394
semantics::{PathResolution, Semantics, SemanticsScope, TypeInfo, VisibleTraits},
@@ -1358,6 +1359,20 @@ impl DefWithBody {
13581359
let field = field.into();
13591360
acc.push(PrivateField { expr, field }.into())
13601361
}
1362+
&hir_ty::InferenceDiagnostic::PrivateAssocItem { id, item } => {
1363+
let expr_or_pat = match id {
1364+
ExprOrPatId::ExprId(expr) => source_map
1365+
.expr_syntax(expr)
1366+
.expect("unexpected synthetic")
1367+
.map(Either::Left),
1368+
ExprOrPatId::PatId(pat) => source_map
1369+
.pat_syntax(pat)
1370+
.expect("unexpected synthetic")
1371+
.map(Either::Right),
1372+
};
1373+
let item = item.into();
1374+
acc.push(PrivateAssocItem { expr_or_pat, item }.into())
1375+
}
13611376
}
13621377
}
13631378
for (expr, mismatch) in infer.expr_type_mismatches() {
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
use either::Either;
2+
3+
use crate::{Diagnostic, DiagnosticsContext};
4+
5+
// Diagnostic: private-assoc-item
6+
//
7+
// This diagnostic is triggered if the referenced associated item is not visible from the current
8+
// module.
9+
pub(crate) fn private_assoc_item(
10+
ctx: &DiagnosticsContext<'_>,
11+
d: &hir::PrivateAssocItem,
12+
) -> Diagnostic {
13+
// FIXME: add quickfix
14+
let name = match d.item.name(ctx.sema.db) {
15+
Some(name) => format!("`{}` ", name),
16+
None => String::new(),
17+
};
18+
Diagnostic::new(
19+
"private-assoc-item",
20+
format!(
21+
"{} {}is private",
22+
match d.item {
23+
hir::AssocItem::Function(_) => "function",
24+
hir::AssocItem::Const(_) => "const",
25+
hir::AssocItem::TypeAlias(_) => "type alias",
26+
},
27+
name,
28+
),
29+
ctx.sema
30+
.diagnostics_display_range(d.expr_or_pat.clone().map(|it| match it {
31+
Either::Left(it) => it.into(),
32+
Either::Right(it) => match it {
33+
Either::Left(it) => it.into(),
34+
Either::Right(it) => it.into(),
35+
},
36+
}))
37+
.range,
38+
)
39+
}
40+
41+
#[cfg(test)]
42+
mod tests {
43+
use crate::tests::check_diagnostics;
44+
45+
#[test]
46+
fn private_method() {
47+
check_diagnostics(
48+
r#"
49+
mod module {
50+
pub struct Struct;
51+
impl Struct {
52+
fn method(&self) {}
53+
}
54+
}
55+
fn main(s: module::Struct) {
56+
s.method();
57+
//^^^^^^^^^^ error: function `method` is private
58+
}
59+
"#,
60+
);
61+
}
62+
63+
#[test]
64+
fn private_func() {
65+
check_diagnostics(
66+
r#"
67+
mod module {
68+
pub struct Struct;
69+
impl Struct {
70+
fn func() {}
71+
}
72+
}
73+
fn main() {
74+
module::Struct::func();
75+
//^^^^^^^^^^^^^^^^^^^^ error: function `func` is private
76+
}
77+
"#,
78+
);
79+
}
80+
81+
#[test]
82+
fn private_const() {
83+
check_diagnostics(
84+
r#"
85+
mod module {
86+
pub struct Struct;
87+
impl Struct {
88+
const CONST: u32 = 0;
89+
}
90+
}
91+
fn main() {
92+
module::Struct::CONST;
93+
//^^^^^^^^^^^^^^^^^^^^^ error: const `CONST` is private
94+
}
95+
"#,
96+
);
97+
}
98+
99+
#[test]
100+
fn private_but_shadowed_in_deref() {
101+
check_diagnostics(
102+
r#"
103+
//- minicore: deref
104+
mod module {
105+
pub struct Struct { field: Inner }
106+
pub struct Inner;
107+
impl core::ops::Deref for Struct {
108+
type Target = Inner;
109+
fn deref(&self) -> &Inner { &self.field }
110+
}
111+
impl Struct {
112+
fn method(&self) {}
113+
}
114+
impl Inner {
115+
pub fn method(&self) {}
116+
}
117+
}
118+
fn main(s: module::Struct) {
119+
s.method();
120+
}
121+
"#,
122+
);
123+
}
124+
}

crates/ide-diagnostics/src/handlers/private_field.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use crate::{Diagnostic, DiagnosticsContext};
22

33
// Diagnostic: private-field
44
//
5-
// This diagnostic is triggered if created structure does not have field provided in record.
5+
// This diagnostic is triggered if the accessed field is not visible from the current module.
66
pub(crate) fn private_field(ctx: &DiagnosticsContext<'_>, d: &hir::PrivateField) -> Diagnostic {
77
// FIXME: add quickfix
88
Diagnostic::new(
@@ -33,6 +33,19 @@ fn main(s: module::Struct) {
3333
);
3434
}
3535

36+
#[test]
37+
fn private_tuple_field() {
38+
check_diagnostics(
39+
r#"
40+
mod module { pub struct Struct(u32); }
41+
fn main(s: module::Struct) {
42+
s.0;
43+
//^^^ error: field `0` of `Struct` is private
44+
}
45+
"#,
46+
);
47+
}
48+
3649
#[test]
3750
fn private_but_shadowed_in_deref() {
3851
check_diagnostics(

crates/ide-diagnostics/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ mod handlers {
3737
pub(crate) mod missing_match_arms;
3838
pub(crate) mod missing_unsafe;
3939
pub(crate) mod no_such_field;
40+
pub(crate) mod private_assoc_item;
4041
pub(crate) mod private_field;
4142
pub(crate) mod replace_filter_map_next_with_find_map;
4243
pub(crate) mod type_mismatch;
@@ -255,6 +256,7 @@ pub fn diagnostics(
255256
AnyDiagnostic::MissingMatchArms(d) => handlers::missing_match_arms::missing_match_arms(&ctx, &d),
256257
AnyDiagnostic::MissingUnsafe(d) => handlers::missing_unsafe::missing_unsafe(&ctx, &d),
257258
AnyDiagnostic::NoSuchField(d) => handlers::no_such_field::no_such_field(&ctx, &d),
259+
AnyDiagnostic::PrivateAssocItem(d) => handlers::private_assoc_item::private_assoc_item(&ctx, &d),
258260
AnyDiagnostic::PrivateField(d) => handlers::private_field::private_field(&ctx, &d),
259261
AnyDiagnostic::ReplaceFilterMapNextWithFindMap(d) => handlers::replace_filter_map_next_with_find_map::replace_filter_map_next_with_find_map(&ctx, &d),
260262
AnyDiagnostic::TypeMismatch(d) => handlers::type_mismatch::type_mismatch(&ctx, &d),

0 commit comments

Comments
 (0)