Skip to content

Commit 55c7545

Browse files
committed
Diagnose private fields in record constructor
1 parent c405509 commit 55c7545

File tree

6 files changed

+84
-27
lines changed

6 files changed

+84
-27
lines changed

crates/hir-def/src/data/adt.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,7 @@ impl VariantData {
447447
}
448448
}
449449

450+
// FIXME: Linear lookup
450451
pub fn field(&self, name: &Name) -> Option<LocalFieldId> {
451452
self.fields().iter().find_map(|(id, data)| if &data.name == name { Some(id) } else { None })
452453
}

crates/hir-ty/src/infer.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ pub(crate) type InferResult<T> = Result<InferOk<T>, TypeError>;
195195
pub enum InferenceDiagnostic {
196196
NoSuchField {
197197
expr: ExprId,
198+
private: bool,
198199
},
199200
PrivateField {
200201
expr: ExprId,

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

Lines changed: 50 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -514,9 +514,6 @@ impl InferenceContext<'_> {
514514
}
515515
Expr::RecordLit { path, fields, spread, .. } => {
516516
let (ty, def_id) = self.resolve_variant(path.as_deref(), false);
517-
if let Some(variant) = def_id {
518-
self.write_variant_resolution(tgt_expr.into(), variant);
519-
}
520517

521518
if let Some(t) = expected.only_has_type(&mut self.table) {
522519
self.unify(&ty, &t);
@@ -526,26 +523,56 @@ impl InferenceContext<'_> {
526523
.as_adt()
527524
.map(|(_, s)| s.clone())
528525
.unwrap_or_else(|| Substitution::empty(Interner));
529-
let field_types = def_id.map(|it| self.db.field_types(it)).unwrap_or_default();
530-
let variant_data = def_id.map(|it| it.variant_data(self.db.upcast()));
531-
for field in fields.iter() {
532-
let field_def =
533-
variant_data.as_ref().and_then(|it| match it.field(&field.name) {
534-
Some(local_id) => Some(FieldId { parent: def_id.unwrap(), local_id }),
535-
None => {
536-
self.push_diagnostic(InferenceDiagnostic::NoSuchField {
537-
expr: field.expr,
538-
});
539-
None
540-
}
541-
});
542-
let field_ty = field_def.map_or(self.err_ty(), |it| {
543-
field_types[it.local_id].clone().substitute(Interner, &substs)
544-
});
545-
// Field type might have some unknown types
546-
// FIXME: we may want to emit a single type variable for all instance of type fields?
547-
let field_ty = self.insert_type_vars(field_ty);
548-
self.infer_expr_coerce(field.expr, &Expectation::has_type(field_ty));
526+
if let Some(variant) = def_id {
527+
self.write_variant_resolution(tgt_expr.into(), variant);
528+
}
529+
match def_id {
530+
Some(_) if fields.is_empty() => {}
531+
Some(def) => {
532+
let field_types = self.db.field_types(def);
533+
let variant_data = def.variant_data(self.db.upcast());
534+
let visibilities = self.db.field_visibilities(def);
535+
for field in fields.iter() {
536+
let field_def = {
537+
match variant_data.field(&field.name) {
538+
Some(local_id) => {
539+
if !visibilities[local_id].is_visible_from(
540+
self.db.upcast(),
541+
self.resolver.module(),
542+
) {
543+
self.push_diagnostic(
544+
InferenceDiagnostic::NoSuchField {
545+
expr: field.expr,
546+
private: true,
547+
},
548+
);
549+
}
550+
Some(FieldId { parent: def, local_id })
551+
}
552+
None => {
553+
self.push_diagnostic(InferenceDiagnostic::NoSuchField {
554+
expr: field.expr,
555+
private: false,
556+
});
557+
None
558+
}
559+
}
560+
};
561+
let field_ty = field_def.map_or(self.err_ty(), |it| {
562+
field_types[it.local_id].clone().substitute(Interner, &substs)
563+
});
564+
565+
// Field type might have some unknown types
566+
// FIXME: we may want to emit a single type variable for all instance of type fields?
567+
let field_ty = self.insert_type_vars(field_ty);
568+
self.infer_expr_coerce(field.expr, &Expectation::has_type(field_ty));
569+
}
570+
}
571+
None => {
572+
for field in fields.iter() {
573+
self.infer_expr_coerce(field.expr, &Expectation::None);
574+
}
575+
}
549576
}
550577
if let Some(expr) = spread {
551578
self.infer_expr(*expr, &Expectation::has_type(ty.clone()));

crates/hir/src/diagnostics.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ pub struct MalformedDerive {
174174
#[derive(Debug)]
175175
pub struct NoSuchField {
176176
pub field: InFile<AstPtr<ast::RecordExprField>>,
177+
pub private: bool,
177178
}
178179

179180
#[derive(Debug)]

crates/hir/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1505,9 +1505,9 @@ impl DefWithBody {
15051505
let expr_syntax = |expr| source_map.expr_syntax(expr).expect("unexpected synthetic");
15061506
for d in &infer.diagnostics {
15071507
match d {
1508-
&hir_ty::InferenceDiagnostic::NoSuchField { expr } => {
1508+
&hir_ty::InferenceDiagnostic::NoSuchField { expr, private } => {
15091509
let field = source_map.field_syntax(expr);
1510-
acc.push(NoSuchField { field }.into())
1510+
acc.push(NoSuchField { field, private }.into())
15111511
}
15121512
&hir_ty::InferenceDiagnostic::MismatchedArgCount { call_expr, expected, found } => {
15131513
acc.push(

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

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,22 @@ use crate::{fix, Assist, Diagnostic, DiagnosticCode, DiagnosticsContext};
1414
pub(crate) fn no_such_field(ctx: &DiagnosticsContext<'_>, d: &hir::NoSuchField) -> Diagnostic {
1515
Diagnostic::new_with_syntax_node_ptr(
1616
ctx,
17-
DiagnosticCode::RustcHardError("E0559"),
18-
"no such field",
17+
if d.private {
18+
DiagnosticCode::RustcHardError("E0451")
19+
} else {
20+
DiagnosticCode::RustcHardError("E0559")
21+
},
22+
if d.private { "field is private" } else { "no such field" },
1923
d.field.clone().map(|it| it.into()),
2024
)
2125
.with_fixes(fixes(ctx, d))
2226
}
2327

2428
fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::NoSuchField) -> Option<Vec<Assist>> {
29+
if d.private {
30+
// FIXME: quickfix to add required visibility
31+
return None;
32+
}
2533
let root = ctx.sema.db.parse_or_expand(d.field.file_id);
2634
missing_record_expr_field_fixes(
2735
&ctx.sema,
@@ -292,6 +300,25 @@ fn main() {
292300
0$0: 0
293301
}
294302
}
303+
"#,
304+
)
305+
}
306+
307+
#[test]
308+
fn test_struct_field_private() {
309+
check_diagnostics(
310+
r#"
311+
mod m {
312+
pub struct Struct {
313+
field: u32
314+
}
315+
}
316+
fn main() {
317+
m::Struct {
318+
field: 0
319+
//^^^^^^^^ error: field is private
320+
};
321+
}
295322
"#,
296323
)
297324
}

0 commit comments

Comments
 (0)