Skip to content

Commit ea77f95

Browse files
committed
Statically assert arguments
1 parent 6f18858 commit ea77f95

File tree

11 files changed

+167
-52
lines changed

11 files changed

+167
-52
lines changed

juniper/src/macros/reflect.rs

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -459,7 +459,7 @@ pub const fn can_be_subtype(ty: WrappedValue, subtype: WrappedValue) -> bool {
459459

460460
/// Extracts an [`Argument`] from the provided [`Arguments`] by its [`Name`].
461461
#[must_use]
462-
pub const fn extract_argument(args: Arguments, name: Name) -> Option<Argument> {
462+
pub const fn get_arg_by_name(args: Arguments, name: Name) -> Option<Argument> {
463463
let mut i = 0;
464464
while i < args.len() {
465465
let arg = args[i];
@@ -694,8 +694,9 @@ macro_rules! assert_subtype {
694694
};
695695
}
696696

697-
/// Asserts validness of the [`Field`]s arguments. See [spec][1] for more
698-
/// info.
697+
/// Asserts validness of the [`Field`]s [`Arguments`].
698+
///
699+
/// See [spec][1] for more info.
699700
///
700701
/// [1]: https://spec.graphql.org/October2021#sel-IAHZhCHCDEEFAAADHD8Cxob
701702
#[macro_export]
@@ -877,33 +878,55 @@ macro_rules! assert_field_args {
877878
};
878879
}
879880

881+
/// Statically asserts whether a [`Field`]'s [`Argument`] represents a `Null`able type, so can be
882+
/// deprecated.
883+
///
884+
/// Must be used in conjunction with the check whether the [`Argument`] has its default value set.
885+
#[doc(hidden)]
880886
#[macro_export]
881-
macro_rules! assert_field_arg_nullable {
887+
macro_rules! assert_field_arg_deprecable {
882888
(
883889
$ty: ty,
884890
$scalar: ty,
885891
$field_name: expr,
886-
$arg_name: expr
887-
$(, $err_prefix: expr)? $(,)?
892+
$arg_name: expr $(,)?
888893
) => {
889-
const {
894+
const _: () = {
890895
const TY_NAME: &::core::primitive::str =
891896
<$ty as $crate::macros::reflect::BaseType<$scalar>>::NAME;
892897
const FIELD_NAME: &::core::primitive::str = $field_name;
893898
const ARGS: $crate::macros::reflect::Arguments =
894899
<$ty as $crate::macros::reflect::FieldMeta<
895900
$scalar,
896-
{ $crate::checked_hash!(FIELD_NAME, $ty, $scalar, $err_prefix) },
901+
{ $crate::checked_hash!(FIELD_NAME, $ty, $scalar) },
897902
>>::ARGUMENTS;
898903
const ARG_NAME: &::core::primitive::str = $arg_name;
899904
const ARG: $crate::macros::reflect::Argument =
900-
$crate::macros::reflect::extract_argument(ARGS, ARG_NAME).unwrap(
905+
$crate::macros::reflect::get_arg_by_name(ARGS, ARG_NAME).expect(
901906
$crate::const_concat!(
902-
$err_prefix, "Field `", FIELD_NAME, "` has no argument `", ARG_NAME, "`",
907+
"field `",
908+
FIELD_NAME,
909+
"` has no argument `",
910+
ARG_NAME,
911+
"`",
903912
),
904913
);
905-
todo!()
906-
}
914+
if ARG.2 % 10 != 2 {
915+
const ARG_TY: &::core::primitive::str = $crate::format_type!(ARG.1, ARG.2);
916+
const ERROR_MSG: &::core::primitive::str = $crate::const_concat!(
917+
"argument `",
918+
ARG_NAME,
919+
"` of `",
920+
TY_NAME,
921+
".",
922+
FIELD_NAME,
923+
"` field cannot be deprecated, because its type `",
924+
ARG_TY,
925+
"` is neither `Null`able nor the default argument value is specified",
926+
);
927+
::core::panic!("{}", ERROR_MSG);
928+
}
929+
};
907930
};
908931
}
909932

@@ -951,7 +974,7 @@ macro_rules! checked_hash {
951974
} else {
952975
const MSG: &str = $crate::const_concat!(
953976
$($prefix,)?
954-
"Field `",
977+
"field `",
955978
$field_name,
956979
"` isn't implemented on `",
957980
<$impl_ty as $crate::macros::reflect::BaseType<$scalar>>::NAME,

juniper_codegen/src/common/field/arg.rs

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -306,20 +306,37 @@ impl OnMethod {
306306
}
307307
}
308308

309-
/// Returns generated code for the [`marker::IsOutputType::mark`] method,
310-
/// which performs static checks for this argument, if it represents an
311-
/// [`OnField`] one.
309+
/// Returns generated code statically asserting deprecability for this argument, if it
310+
/// represents an [`OnField`] one.
311+
#[must_use]
312+
pub(crate) fn assert_deprecable_tokens(
313+
&self,
314+
ty: &syn::Type,
315+
const_scalar: &syn::Type,
316+
field_name: &str,
317+
) -> Option<TokenStream> {
318+
let arg = self.as_regular()?;
319+
(arg.deprecated.is_some() && arg.default.is_none()).then(|| {
320+
let arg_ty = &arg.ty;
321+
let arg_name = &arg.name;
322+
quote_spanned! { arg_ty.span() =>
323+
::juniper::assert_field_arg_deprecable!(
324+
#ty,
325+
#const_scalar,
326+
#field_name,
327+
#arg_name,
328+
);
329+
}
330+
})
331+
}
332+
333+
/// Returns generated code for the [`marker::IsOutputType::mark`] method, which performs static
334+
/// checks for this argument, if it represents an [`OnField`] one.
312335
///
313336
/// [`marker::IsOutputType::mark`]: juniper::marker::IsOutputType::mark
314337
#[must_use]
315338
pub(crate) fn method_mark_tokens(&self, scalar: &scalar::Type) -> Option<TokenStream> {
316-
let arg = self.as_regular()?;
317-
let ty = &arg.ty;
318-
319-
if arg.deprecated.is_some() && arg.default.is_none() {
320-
// TODO: Panic in compile time if not `Null`able.
321-
}
322-
339+
let ty = &self.as_regular()?.ty;
323340
Some(quote_spanned! { ty.span() =>
324341
<#ty as ::juniper::marker::IsInputType<#scalar>>::mark();
325342
})

juniper_codegen/src/graphql_interface/mod.rs

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -610,12 +610,19 @@ impl Definition {
610610
let (impl_generics, _, where_clause) = generics.split_for_impl();
611611
let (_, ty_generics, _) = self.generics.split_for_impl();
612612
let ty_const_generics = self.const_trait_generics();
613+
let reflectable_ty: syn::Type = parse_quote! { #ty #ty_const_generics };
613614

614615
let fields_marks = self
615616
.fields
616617
.iter()
617618
.map(|f| f.method_mark_tokens(false, scalar));
618619

620+
let assert_args_deprecable = self.fields.iter().flat_map(|field| {
621+
field.arguments.iter().flatten().filter_map(|arg| {
622+
arg.assert_deprecable_tokens(&reflectable_ty, const_scalar, &field.name)
623+
})
624+
});
625+
619626
let is_output = self.implemented_for.iter().map(|impler| {
620627
quote_spanned! { impler.span() =>
621628
<#impler as ::juniper::marker::IsOutputType<#scalar>>::mark();
@@ -639,7 +646,7 @@ impl Definition {
639646
quote_spanned! { const_impl_for.span() =>
640647
::juniper::assert_transitive_impls!(
641648
#const_scalar,
642-
#ty #ty_const_generics,
649+
#reflectable_ty,
643650
#const_impl_for,
644651
#( #const_implements ),*
645652
);
@@ -654,15 +661,16 @@ impl Definition {
654661
{
655662
fn mark() {
656663
#( #fields_marks )*
664+
#( #assert_args_deprecable )*
657665
#( #is_output )*
658666
::juniper::assert_interfaces_impls!(
659667
#const_scalar,
660-
#ty #ty_const_generics,
668+
#reflectable_ty,
661669
#( #const_impl_for ),*
662670
);
663671
::juniper::assert_implemented_for!(
664672
#const_scalar,
665-
#ty #ty_const_generics,
673+
#reflectable_ty,
666674
#( #const_implements ),*
667675
);
668676
#( #transitive_checks )*
@@ -1017,8 +1025,8 @@ impl Definition {
10171025
.collect()
10181026
}
10191027

1020-
/// Returns generated code implementing [`Field`] trait for each field of
1021-
/// this [GraphQL interface][1].
1028+
/// Returns generated code implementing [`Field`] trait for each field of this
1029+
/// [GraphQL interface][1].
10221030
///
10231031
/// [`Field`]: juniper::macros::reflect::Field
10241032
/// [1]: https://spec.graphql.org/October2021#sec-Interfaces
@@ -1053,7 +1061,7 @@ impl Definition {
10531061
let mut return_ty = field.ty.clone();
10541062
generics.replace_type_with_defaults(&mut return_ty);
10551063

1056-
let const_ty_generics = self.const_trait_generics();
1064+
let ty_const_generics = self.const_trait_generics();
10571065

10581066
let unreachable_arm = (self.implemented_for.is_empty()
10591067
|| !self.generics.params.is_empty())
@@ -1077,7 +1085,7 @@ impl Definition {
10771085
match self {
10781086
#( #ty::#implemented_for_idents(v) => {
10791087
::juniper::assert_field!(
1080-
#ty #const_ty_generics,
1088+
#ty #ty_const_generics,
10811089
#const_implemented_for,
10821090
#const_scalar,
10831091
#field_name,
@@ -1097,8 +1105,8 @@ impl Definition {
10971105
.collect()
10981106
}
10991107

1100-
/// Returns generated code implementing [`AsyncField`] trait for each field
1101-
/// of this [GraphQL interface][1].
1108+
/// Returns generated code implementing [`AsyncField`] trait for each field of this
1109+
/// [GraphQL interface][1].
11021110
///
11031111
/// [`AsyncField`]: juniper::macros::reflect::AsyncField
11041112
/// [1]: https://spec.graphql.org/October2021#sec-Interfaces
@@ -1133,7 +1141,7 @@ impl Definition {
11331141
let mut return_ty = field.ty.clone();
11341142
generics.replace_type_with_defaults(&mut return_ty);
11351143

1136-
let const_ty_generics = self.const_trait_generics();
1144+
let ty_const_generics = self.const_trait_generics();
11371145

11381146
let unreachable_arm = (self.implemented_for.is_empty()
11391147
|| !self.generics.params.is_empty())
@@ -1157,7 +1165,7 @@ impl Definition {
11571165
match self {
11581166
#( #ty::#implemented_for_idents(v) => {
11591167
::juniper::assert_field!(
1160-
#ty #const_ty_generics,
1168+
#ty #ty_const_generics,
11611169
#const_implemented_for,
11621170
#const_scalar,
11631171
#field_name,

juniper_codegen/src/graphql_object/mod.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,7 @@ impl<Operation: ?Sized + 'static> Definition<Operation> {
332332
#[must_use]
333333
pub(crate) fn impl_output_type_tokens(&self) -> TokenStream {
334334
let scalar = &self.scalar;
335+
let const_scalar = &self.scalar.default_ty();
335336

336337
let (impl_generics, where_clause) = self.impl_generics(false);
337338
let ty = &self.ty;
@@ -342,6 +343,14 @@ impl<Operation: ?Sized + 'static> Definition<Operation> {
342343
.iter()
343344
.map(|f| f.method_mark_tokens(coerce_result, scalar));
344345

346+
let assert_args_deprecable = self.fields.iter().flat_map(|field| {
347+
field
348+
.arguments
349+
.iter()
350+
.flatten()
351+
.filter_map(|arg| arg.assert_deprecable_tokens(ty, const_scalar, &field.name))
352+
});
353+
345354
let interface_tys = self.interfaces.iter();
346355

347356
quote! {
@@ -350,6 +359,7 @@ impl<Operation: ?Sized + 'static> Definition<Operation> {
350359
{
351360
fn mark() {
352361
#( #fields_marks )*
362+
#( #assert_args_deprecable )*
353363
#( <#interface_tys as ::juniper::marker::IsOutputType<#scalar>>::mark(); )*
354364
}
355365
}

tests/codegen/fail/interface/struct/attr_missing_field.stderr

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,28 @@
1-
error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`.
1+
error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: field `id` isn't implemented on `ObjA`.
22
--> fail/interface/struct/attr_missing_field.rs:11:5
33
|
44
11 | id: String,
55
| ^^ evaluation of `<CharacterValueEnum<ObjA> as juniper::macros::reflect::Field<__S, id>>::call::_::IMPL_ARGS::{constant#0}` failed here
66
|
77
= note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info)
88

9-
error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`.
9+
error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: field `id` isn't implemented on `ObjA`.
1010
--> fail/interface/struct/attr_missing_field.rs:11:5
1111
|
1212
11 | id: String,
1313
| ^^ evaluation of `<CharacterValueEnum<ObjA> as juniper::macros::reflect::Field<__S, id>>::call::_::IMPL_RETURN_TY::{constant#0}` failed here
1414
|
1515
= note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info)
1616

17-
error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`.
17+
error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: field `id` isn't implemented on `ObjA`.
1818
--> fail/interface/struct/attr_missing_field.rs:11:5
1919
|
2020
11 | id: String,
2121
| ^^ evaluation of `<CharacterValueEnum<ObjA> as juniper::macros::reflect::AsyncField<__S, id>>::call::_::IMPL_ARGS::{constant#0}` failed here
2222
|
2323
= note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info)
2424

25-
error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`.
25+
error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: field `id` isn't implemented on `ObjA`.
2626
--> fail/interface/struct/attr_missing_field.rs:11:5
2727
|
2828
11 | id: String,
@@ -39,7 +39,7 @@ error[E0277]: the trait bound `ObjA: reflect::Field<__S, 11301463986558097276003
3939
= help: the trait `Field<__S, 11301463986558097276003903130001171064>` is not implemented for `ObjA`
4040
but trait `Field<__S, 140650918148392961738240285796466530725>` is implemented for it
4141

42-
error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`.
42+
error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: field `id` isn't implemented on `ObjA`.
4343
--> fail/interface/struct/attr_missing_field.rs:11:5
4444
|
4545
11 | id: String,
@@ -56,7 +56,7 @@ error[E0277]: the trait bound `ObjA: AsyncField<__S, 113014639865580972760039031
5656
= help: the trait `AsyncField<__S, 11301463986558097276003903130001171064>` is not implemented for `ObjA`
5757
but trait `AsyncField<__S, 140650918148392961738240285796466530725>` is implemented for it
5858

59-
error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`.
59+
error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: field `id` isn't implemented on `ObjA`.
6060
--> fail/interface/struct/attr_missing_field.rs:11:5
6161
|
6262
11 | id: String,

tests/codegen/fail/interface/struct/derive_missing_field.stderr

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,28 @@
1-
error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`.
1+
error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: field `id` isn't implemented on `ObjA`.
22
--> fail/interface/struct/derive_missing_field.rs:12:5
33
|
44
12 | id: String,
55
| ^^ evaluation of `<CharacterValueEnum<ObjA> as juniper::macros::reflect::Field<__S, id>>::call::_::IMPL_ARGS::{constant#0}` failed here
66
|
77
= note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info)
88

9-
error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`.
9+
error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: field `id` isn't implemented on `ObjA`.
1010
--> fail/interface/struct/derive_missing_field.rs:12:5
1111
|
1212
12 | id: String,
1313
| ^^ evaluation of `<CharacterValueEnum<ObjA> as juniper::macros::reflect::Field<__S, id>>::call::_::IMPL_RETURN_TY::{constant#0}` failed here
1414
|
1515
= note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info)
1616

17-
error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`.
17+
error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: field `id` isn't implemented on `ObjA`.
1818
--> fail/interface/struct/derive_missing_field.rs:12:5
1919
|
2020
12 | id: String,
2121
| ^^ evaluation of `<CharacterValueEnum<ObjA> as juniper::macros::reflect::AsyncField<__S, id>>::call::_::IMPL_ARGS::{constant#0}` failed here
2222
|
2323
= note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info)
2424

25-
error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`.
25+
error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: field `id` isn't implemented on `ObjA`.
2626
--> fail/interface/struct/derive_missing_field.rs:12:5
2727
|
2828
12 | id: String,
@@ -39,7 +39,7 @@ error[E0277]: the trait bound `ObjA: reflect::Field<__S, 11301463986558097276003
3939
= help: the trait `Field<__S, 11301463986558097276003903130001171064>` is not implemented for `ObjA`
4040
but trait `Field<__S, 140650918148392961738240285796466530725>` is implemented for it
4141

42-
error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`.
42+
error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: field `id` isn't implemented on `ObjA`.
4343
--> fail/interface/struct/derive_missing_field.rs:12:5
4444
|
4545
12 | id: String,
@@ -56,7 +56,7 @@ error[E0277]: the trait bound `ObjA: AsyncField<__S, 113014639865580972760039031
5656
= help: the trait `AsyncField<__S, 11301463986558097276003903130001171064>` is not implemented for `ObjA`
5757
but trait `AsyncField<__S, 140650918148392961738240285796466530725>` is implemented for it
5858

59-
error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`.
59+
error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: field `id` isn't implemented on `ObjA`.
6060
--> fail/interface/struct/derive_missing_field.rs:12:5
6161
|
6262
12 | id: String,
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
use juniper::graphql_interface;
2+
3+
#[graphql_interface]
4+
trait Character {
5+
fn id(&self, #[graphql(deprecated)] num: i32) -> &str;
6+
async fn name(&self, #[graphql(deprecated = "reason")] pre: Vec<String>) -> &str;
7+
}
8+
9+
fn main() {}

0 commit comments

Comments
 (0)