diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index dc1acade85ed5..d846a5acb37df 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -1750,9 +1750,10 @@ impl<'hir> LoweringContext<'_, 'hir> { ); hir::TyKind::Err(guar) } - TyKind::View(ty, _) => { - // FIXME(scrabsha): lower view types to HIR. - return self.lower_ty(ty, itctx); + TyKind::View(ty, fields) => { + let ty = self.lower_ty_alloc(ty, itctx); + let fields = self.arena.alloc_slice(fields); + hir::TyKind::View(ty, fields) } TyKind::Dummy => panic!("`TyKind::Dummy` should never be lowered"), }; diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index ee401376a6a5c..236ccca1a517e 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -3960,6 +3960,8 @@ pub enum TyKind<'hir, Unambig = ()> { /// /// The optional ident is the variant when an enum is passed `field_of!(Enum, Variant.field)`. FieldOf(&'hir Ty<'hir>, &'hir TyFieldPath), + /// A view of a type. `T.{ field_1, field_2 }`. + View(&'hir Ty<'hir>, &'hir [Ident]), /// `TyKind::Infer` means the type should be inferred instead of it having been /// specified. This can appear anywhere in a type. /// diff --git a/compiler/rustc_hir/src/intravisit.rs b/compiler/rustc_hir/src/intravisit.rs index 44501b1aa892b..df410d680f533 100644 --- a/compiler/rustc_hir/src/intravisit.rs +++ b/compiler/rustc_hir/src/intravisit.rs @@ -1053,6 +1053,12 @@ pub fn walk_ty<'v, V: Visitor<'v>>(visitor: &mut V, typ: &'v Ty<'v, AmbigArg>) - visit_opt!(visitor, visit_ident, *variant); try_visit!(visitor.visit_ident(*field)); } + TyKind::View(ty, fields) => { + try_visit!(visitor.visit_ty_unambig(ty)); + for field in fields { + try_visit!(visitor.visit_ident(*field)); + } + } } V::Result::output() } diff --git a/compiler/rustc_hir_analysis/src/diagnostics.rs b/compiler/rustc_hir_analysis/src/diagnostics.rs index 1bfc495071284..a671b02b5fa66 100644 --- a/compiler/rustc_hir_analysis/src/diagnostics.rs +++ b/compiler/rustc_hir_analysis/src/diagnostics.rs @@ -2000,3 +2000,33 @@ impl Diagnostic<'_, G> for UncoveredTyParam<'_> { diag } } + +#[derive(Diagnostic)] +#[diag("field `{$name}` is already part of the view")] +pub(crate) struct ViewedFieldIsAlreadyPartOfTheView { + #[primary_span] + pub span: Span, + pub name: Symbol, + #[label("field `{$name}` is declared as viewed here")] + pub previous_field_span: Span, +} + +#[derive(Diagnostic)] +#[diag("only structs can be viewed")] +pub(crate) struct OnlyStructsCanBeViewedNonAdt<'tcx> { + #[primary_span] + #[label("type `{$ty}` cannot be viewed")] + pub span: Span, + pub ty: Ty<'tcx>, +} + +#[derive(Diagnostic)] +#[diag("only structs can be viewed")] +pub(crate) struct OnlyStructsCanBeViewedAdt<'tcx> { + #[primary_span] + #[label("`{$ty}` is {$article} {$kind}, it cannot be viewed")] + pub span: Span, + pub ty: Ty<'tcx>, + pub article: &'static str, + pub kind: &'static str, +} diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs index 2b0219f356c3c..7409ea620228d 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs @@ -51,7 +51,7 @@ use rustc_trait_selection::traits::{self, FulfillmentError}; use tracing::{debug, instrument}; use crate::check::check_abi; -use crate::diagnostics::{BadReturnTypeNotation, NoFieldOnType}; +use crate::diagnostics::{self, BadReturnTypeNotation, NoFieldOnType}; use crate::hir_ty_lowering::errors::{GenericsArgsErrExtend, prohibit_assoc_item_constraint}; use crate::hir_ty_lowering::generics::{check_generic_arg_count, lower_generic_args}; use crate::middle::resolve_bound_vars as rbv; @@ -3401,6 +3401,10 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { *variant, *field, ), + hir::TyKind::View(ty, fields) => { + self.lower_view(self.lower_ty(ty), fields, hir_ty.span) + } + hir::TyKind::Err(guar) => Ty::new_error(tcx, *guar), }; @@ -3812,4 +3816,73 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { let adt_ty = Ty::new_adt(tcx, adt_def, args); ty::Const::new_value(tcx, valtree, adt_ty) } + + fn lower_view(&self, inner_ty: Ty<'tcx>, fields: &[Ident], ty_span: Span) -> Ty<'tcx> { + // Step 1: check that every field is unique, and keep a list of field that we know are + // unique. + let mut viewed_fields = Vec::::with_capacity(fields.len()); + + for f in fields { + let f = f.normalize_to_macros_2_0(); + // PERF: this is quadratic, but ~fine since the amount of fields is very low. + if let Some(previous_field_span) = + viewed_fields.iter().find_map(|f_| (*f_ == f).then_some(f_.span)) + { + self.dcx().emit_err(diagnostics::ViewedFieldIsAlreadyPartOfTheView { + name: f.name, + span: f.span, + previous_field_span, + }); + continue; + } + viewed_fields.push(f); + } + + // Step 2: check that the viewed type is a struct. + let variant = match inner_ty.kind() { + ty::Adt(def, _) if def.is_struct() => def.non_enum_variant(), + + ty::Adt(def, _) => { + let guar = self.dcx().emit_err(diagnostics::OnlyStructsCanBeViewedAdt { + ty: inner_ty, + span: ty_span, + article: def.article(), + kind: def.descr(), + }); + return Ty::new_error(self.tcx(), guar); + } + + _ => { + let guar = self.dcx().emit_err(diagnostics::OnlyStructsCanBeViewedNonAdt { + ty: inner_ty, + span: ty_span, + }); + return Ty::new_error(self.tcx(), guar); + } + }; + + // Step 3: check that every viewed field exists. + let mut viewed_indices = Vec::with_capacity(viewed_fields.len()); + let mut error = None; + for field in viewed_fields { + let Some((_, field)) = variant + .fields + .iter_enumerated() + .find(|(_, f)| f.ident(self.tcx()).normalize_to_macros_2_0() == field) + else { + let err = + self.dcx().emit_err(NoFieldOnType { span: field.span, field, ty: inner_ty }); + error = Some(err); + continue; + }; + + viewed_indices.push(field); + } + if let Some(guar) = error { + return Ty::new_error(self.tcx(), guar); + } + + // FIXME(scrabsha): actually lower view types. + inner_ty + } } diff --git a/compiler/rustc_hir_pretty/src/lib.rs b/compiler/rustc_hir_pretty/src/lib.rs index 136fe22eea60f..447ad2447b3b9 100644 --- a/compiler/rustc_hir_pretty/src/lib.rs +++ b/compiler/rustc_hir_pretty/src/lib.rs @@ -476,6 +476,17 @@ impl<'a> State<'a> { self.print_ident(*field); self.word(")"); } + hir::TyKind::View(ty, fields) => { + self.word("view_type!("); + self.print_type(ty); + self.word(".{"); + if !fields.is_empty() { + self.space(); + self.commasep(Breaks::Inconsistent, fields, |s, f| s.print_ident(*f)); + self.space(); + } + self.word("})"); + } } self.end(ib) } diff --git a/compiler/rustc_middle/src/ty/adt.rs b/compiler/rustc_middle/src/ty/adt.rs index d380b7f522636..a0daa04844e03 100644 --- a/compiler/rustc_middle/src/ty/adt.rs +++ b/compiler/rustc_middle/src/ty/adt.rs @@ -335,6 +335,17 @@ impl From for DataTypeKind { } } +impl AdtKind { + pub fn article(self) -> &'static str { + match self { + AdtKind::Struct => "a", + // https://english.stackexchange.com/a/266324 + AdtKind::Union => "a", + AdtKind::Enum => "an", + } + } +} + impl AdtDefData { /// Creates a new `AdtDefData`. pub(super) fn new( @@ -455,6 +466,14 @@ impl<'tcx> AdtDef<'tcx> { } } + /// Returns a description of this abstract data type with the article. + pub fn article(self) -> &'static str { + match self.adt_kind() { + AdtKind::Struct | AdtKind::Union => "a", + AdtKind::Enum => "an", + } + } + /// Returns a description of a variant of this abstract data type. #[inline] pub fn variant_descr(self) -> &'static str { diff --git a/compiler/rustc_passes/src/input_stats.rs b/compiler/rustc_passes/src/input_stats.rs index d2bcf0359342c..39601534d0c85 100644 --- a/compiler/rustc_passes/src/input_stats.rs +++ b/compiler/rustc_passes/src/input_stats.rs @@ -411,6 +411,7 @@ impl<'v> hir_visit::Visitor<'v> for StatCollector<'v> { Infer, Pat, FieldOf, + View, Err ] ); diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index 3179bda868651..c7387bf157d10 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -1953,6 +1953,10 @@ pub(crate) fn clean_ty<'tcx>(ty: &hir::Ty<'tcx>, cx: &mut DocContext<'tcx>) -> T TyKind::UnsafeBinder(unsafe_binder_ty) => { UnsafeBinder(Box::new(clean_unsafe_binder_ty(unsafe_binder_ty, cx))) } + TyKind::View(ty, _) => { + // FIXME(scrabsha): propagate view types to `rustdoc`. + clean_ty(ty, cx) + } // Rustdoc handles `TyKind::Err`s by turning them into `Type::Infer`s. TyKind::Infer(()) | TyKind::Err(_) diff --git a/src/tools/clippy/clippy_lints/src/dereference.rs b/src/tools/clippy/clippy_lints/src/dereference.rs index fbff41fe00914..0868ee322a646 100644 --- a/src/tools/clippy/clippy_lints/src/dereference.rs +++ b/src/tools/clippy/clippy_lints/src/dereference.rs @@ -902,6 +902,10 @@ impl TyCoercionStability { | TyKind::TraitObject(..) | TyKind::InferDelegation(..) | TyKind::Err(_) => Self::Reborrow, + TyKind::View(ty, _) => { + // FIXME(scrabsha): what are the semantics of view types here? + Self::for_hir_ty(ty) + } TyKind::UnsafeBinder(..) => Self::None, }; } diff --git a/src/tools/clippy/clippy_utils/src/hir_utils.rs b/src/tools/clippy/clippy_utils/src/hir_utils.rs index 050e43344d0c4..7c3fa51228bc9 100644 --- a/src/tools/clippy/clippy_utils/src/hir_utils.rs +++ b/src/tools/clippy/clippy_utils/src/hir_utils.rs @@ -1650,6 +1650,10 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { TyKind::UnsafeBinder(binder) => { self.hash_ty(binder.inner_ty); }, + TyKind::View(ty, _) => { + self.hash_ty(ty); + // FIXME(scrabsha): probably hash the fields as well? + }, TyKind::Err(_) | TyKind::Infer(()) | TyKind::Never diff --git a/tests/ui/view-types/auxiliary/hygiene.rs b/tests/ui/view-types/auxiliary/hygiene.rs new file mode 100644 index 0000000000000..cf842b56e6970 --- /dev/null +++ b/tests/ui/view-types/auxiliary/hygiene.rs @@ -0,0 +1,12 @@ +//@ edition:2015 + +#![feature(view_types, view_type_macro)] + +pub use std::view::view_type; + +#[macro_export] +macro_rules! view_bar { + () => { + $crate::view_type!(Bar.{ async }) + } +} diff --git a/tests/ui/view-types/duplicate_field.rs b/tests/ui/view-types/duplicate_field.rs new file mode 100644 index 0000000000000..b45580510b6cb --- /dev/null +++ b/tests/ui/view-types/duplicate_field.rs @@ -0,0 +1,30 @@ +#![feature(view_types, view_type_macro)] +#![allow(unused)] + +use std::view::view_type; + +struct Foo { + bar: usize, +} + +struct Pair(usize); + +fn f( + _foo: &mut view_type!(Foo.{ bar, bar }), + //~^ ERROR field `bar` is already part of the view + _pair: &mut view_type!(Pair.{ 0, 0 }), + //~^ ERROR field `0` is already part of the view +) { +} + +impl Foo { + fn f(self: &mut view_type!(Self.{ bar, bar })) {} + //~^ ERROR field `bar` is already part of the view +} + +impl Pair { + fn f(self: &mut view_type!(Self.{ 0, 0 })) {} + //~^ ERROR field `0` is already part of the view +} + +fn main() {} diff --git a/tests/ui/view-types/duplicate_field.stderr b/tests/ui/view-types/duplicate_field.stderr new file mode 100644 index 0000000000000..f0162f6e0a67d --- /dev/null +++ b/tests/ui/view-types/duplicate_field.stderr @@ -0,0 +1,34 @@ +error: field `bar` is already part of the view + --> $DIR/duplicate_field.rs:13:38 + | +LL | _foo: &mut view_type!(Foo.{ bar, bar }), + | --- ^^^ + | | + | field `bar` is declared as viewed here + +error: field `0` is already part of the view + --> $DIR/duplicate_field.rs:15:38 + | +LL | _pair: &mut view_type!(Pair.{ 0, 0 }), + | - ^ + | | + | field `0` is declared as viewed here + +error: field `bar` is already part of the view + --> $DIR/duplicate_field.rs:21:44 + | +LL | fn f(self: &mut view_type!(Self.{ bar, bar })) {} + | --- ^^^ + | | + | field `bar` is declared as viewed here + +error: field `0` is already part of the view + --> $DIR/duplicate_field.rs:26:42 + | +LL | fn f(self: &mut view_type!(Self.{ 0, 0 })) {} + | - ^ + | | + | field `0` is declared as viewed here + +error: aborting due to 4 previous errors + diff --git a/tests/ui/view-types/hygiene.rs b/tests/ui/view-types/hygiene.rs new file mode 100644 index 0000000000000..15a9f7f228f90 --- /dev/null +++ b/tests/ui/view-types/hygiene.rs @@ -0,0 +1,32 @@ +//@ run-pass +//@ aux-build: hygiene.rs + +#![feature(view_types, view_type_macro, decl_macro)] +#![allow(unused)] + +extern crate hygiene; + +use std::view::view_type; + +pub macro what($name: ident) { + struct Foo { + bar: usize, + $name: usize, + } + + impl Foo { + fn f(self: &mut view_type!(Self.{ bar, $name })) {} + } +} + +what!(bar); + +struct Bar { + r#async: (), +} + +impl Bar { + fn f(self: hygiene::view_bar!()) {} +} + +fn main() {} diff --git a/tests/ui/view-types/must-be-struct.rs b/tests/ui/view-types/must-be-struct.rs index 43dbae587e4ab..fb30ef1234cc5 100644 --- a/tests/ui/view-types/must-be-struct.rs +++ b/tests/ui/view-types/must-be-struct.rs @@ -1,6 +1,3 @@ -//@ known-bug: unknown -//@ run-pass - #![feature(view_types, view_type_macro)] #![allow(unused)] @@ -11,9 +8,11 @@ enum Foo { Baz, } -// The following types are not structs, we expect errors here. fn f(_: view_type!(Foo.{})) {} +//~^ ERROR only structs can be viewed fn g(_: view_type!(u8.{})) {} +//~^ ERROR only structs can be viewed fn h(_: view_type!(char.{})) {} +//~^ ERROR only structs can be viewed fn main() {} diff --git a/tests/ui/view-types/must-be-struct.stderr b/tests/ui/view-types/must-be-struct.stderr new file mode 100644 index 0000000000000..3d47e4903af6c --- /dev/null +++ b/tests/ui/view-types/must-be-struct.stderr @@ -0,0 +1,20 @@ +error: only structs can be viewed + --> $DIR/must-be-struct.rs:11:9 + | +LL | fn f(_: view_type!(Foo.{})) {} + | ^^^^^^^^^^^^^^^^^^ `Foo` is an enum, it cannot be viewed + +error: only structs can be viewed + --> $DIR/must-be-struct.rs:13:9 + | +LL | fn g(_: view_type!(u8.{})) {} + | ^^^^^^^^^^^^^^^^^ type `u8` cannot be viewed + +error: only structs can be viewed + --> $DIR/must-be-struct.rs:15:9 + | +LL | fn h(_: view_type!(char.{})) {} + | ^^^^^^^^^^^^^^^^^^^ type `char` cannot be viewed + +error: aborting due to 3 previous errors + diff --git a/tests/ui/view-types/must-exist.rs b/tests/ui/view-types/must-exist.rs index 05792b01b3265..efb43eb509e9c 100644 --- a/tests/ui/view-types/must-exist.rs +++ b/tests/ui/view-types/must-exist.rs @@ -1,6 +1,3 @@ -//@ known-bug: unknown -//@ run-pass - #![feature(view_types, view_type_macro)] #![allow(unused)] @@ -10,8 +7,9 @@ struct S { foo: (), } -// We expect errors here, since `S` has no field `bar`. fn f(_: view_type!(S.{ bar })) {} +//~^ ERROR no field `bar` on type `S` fn g(_: view_type!(S.{ foo, bar })) {} +//~^ ERROR no field `bar` on type `S` fn main() {} diff --git a/tests/ui/view-types/must-exist.stderr b/tests/ui/view-types/must-exist.stderr new file mode 100644 index 0000000000000..a775ad84a7edf --- /dev/null +++ b/tests/ui/view-types/must-exist.stderr @@ -0,0 +1,15 @@ +error[E0609]: no field `bar` on type `S` + --> $DIR/must-exist.rs:10:24 + | +LL | fn f(_: view_type!(S.{ bar })) {} + | ^^^ + +error[E0609]: no field `bar` on type `S` + --> $DIR/must-exist.rs:12:29 + | +LL | fn g(_: view_type!(S.{ foo, bar })) {} + | ^^^ + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0609`. diff --git a/tests/ui/view-types/syntax-errors.rs b/tests/ui/view-types/syntax-errors.rs index 19f5061cf820a..18ccb859d549e 100644 --- a/tests/ui/view-types/syntax-errors.rs +++ b/tests/ui/view-types/syntax-errors.rs @@ -9,13 +9,17 @@ struct Foo { } impl Foo { - fn not_a_field(self: &mut view_type!(Foo.{ _ }), _: &mut view_type!(Foo.{ _ })) {} - //~^ ERROR expected identifier - //~| ERROR expected identifier + fn not_a_field(self: &mut view_type!(Self.{ _ }), _: &mut view_type!(Foo.{ _ })) {} + //~^ ERROR expected identifier, found reserved identifier + //~| ERROR expected identifier, found reserved identifier + //~| ERROR no field `_` on type `Foo` + //~| ERROR no field `_` on type `Foo` fn keyword(self: &mut view_type!(Foo.{ where }), _: &mut view_type!(Foo.{ for })) {} //~^ ERROR expected identifier //~| ERROR expected identifier + //~| ERROR no field `r#where` on type `Foo` + //~| ERROR no field `r#for` on type `Foo` fn no_comma(self: &mut view_type!(Foo.{ bar baz }), _: &mut view_type!(Foo.{ bar baz })) {} //~^ ERROR expected one of diff --git a/tests/ui/view-types/syntax-errors.stderr b/tests/ui/view-types/syntax-errors.stderr index 86c167ca9a879..1c1e5fdafae1a 100644 --- a/tests/ui/view-types/syntax-errors.stderr +++ b/tests/ui/view-types/syntax-errors.stderr @@ -1,17 +1,17 @@ error: expected identifier, found reserved identifier `_` - --> $DIR/syntax-errors.rs:12:48 + --> $DIR/syntax-errors.rs:12:49 | -LL | fn not_a_field(self: &mut view_type!(Foo.{ _ }), _: &mut view_type!(Foo.{ _ })) {} - | ^ expected identifier, found reserved identifier +LL | fn not_a_field(self: &mut view_type!(Self.{ _ }), _: &mut view_type!(Foo.{ _ })) {} + | ^ expected identifier, found reserved identifier error: expected identifier, found reserved identifier `_` - --> $DIR/syntax-errors.rs:12:79 + --> $DIR/syntax-errors.rs:12:80 | -LL | fn not_a_field(self: &mut view_type!(Foo.{ _ }), _: &mut view_type!(Foo.{ _ })) {} - | ^ expected identifier, found reserved identifier +LL | fn not_a_field(self: &mut view_type!(Self.{ _ }), _: &mut view_type!(Foo.{ _ })) {} + | ^ expected identifier, found reserved identifier error: expected identifier, found keyword `where` - --> $DIR/syntax-errors.rs:16:44 + --> $DIR/syntax-errors.rs:18:44 | LL | fn keyword(self: &mut view_type!(Foo.{ where }), _: &mut view_type!(Foo.{ for })) {} | ^^^^^ expected identifier, found keyword @@ -22,7 +22,7 @@ LL | fn keyword(self: &mut view_type!(Foo.{ r#where }), _: &mut view_type!(F | ++ error: expected identifier, found keyword `for` - --> $DIR/syntax-errors.rs:16:79 + --> $DIR/syntax-errors.rs:18:79 | LL | fn keyword(self: &mut view_type!(Foo.{ where }), _: &mut view_type!(Foo.{ for })) {} | ^^^ expected identifier, found keyword @@ -33,7 +33,7 @@ LL | fn keyword(self: &mut view_type!(Foo.{ where }), _: &mut view_type!(Foo | ++ error: expected one of `,` or `}`, found `baz` - --> $DIR/syntax-errors.rs:20:49 + --> $DIR/syntax-errors.rs:24:49 | LL | fn no_comma(self: &mut view_type!(Foo.{ bar baz }), _: &mut view_type!(Foo.{ bar baz })) {} | -^^^ expected one of `,` or `}` @@ -41,12 +41,37 @@ LL | fn no_comma(self: &mut view_type!(Foo.{ bar baz }), _: &mut view_type!( | help: missing `,` error: expected one of `,` or `}`, found `baz` - --> $DIR/syntax-errors.rs:20:86 + --> $DIR/syntax-errors.rs:24:86 | LL | fn no_comma(self: &mut view_type!(Foo.{ bar baz }), _: &mut view_type!(Foo.{ bar baz })) {} | -^^^ expected one of `,` or `}` | | | help: missing `,` -error: aborting due to 6 previous errors +error[E0609]: no field `_` on type `Foo` + --> $DIR/syntax-errors.rs:12:49 + | +LL | fn not_a_field(self: &mut view_type!(Self.{ _ }), _: &mut view_type!(Foo.{ _ })) {} + | ^ + +error[E0609]: no field `_` on type `Foo` + --> $DIR/syntax-errors.rs:12:80 + | +LL | fn not_a_field(self: &mut view_type!(Self.{ _ }), _: &mut view_type!(Foo.{ _ })) {} + | ^ + +error[E0609]: no field `r#where` on type `Foo` + --> $DIR/syntax-errors.rs:18:44 + | +LL | fn keyword(self: &mut view_type!(Foo.{ where }), _: &mut view_type!(Foo.{ for })) {} + | ^^^^^ + +error[E0609]: no field `r#for` on type `Foo` + --> $DIR/syntax-errors.rs:18:79 + | +LL | fn keyword(self: &mut view_type!(Foo.{ where }), _: &mut view_type!(Foo.{ for })) {} + | ^^^ + +error: aborting due to 10 previous errors +For more information about this error, try `rustc --explain E0609`.