Skip to content

Commit f1debb1

Browse files
fix(compiler): raise diagnostics when using null in non-nullable position (#746)
* chore(compiler): add regression test for #738 * chore(compiler): remove 2 suffix from method names * fix(compiler): raise a diagnostic for null items in a non-nullable list * clippy * test nested list inside object * fix(compiler): null type is never assignable to a non-null type and always assignable to a nullable type * chore(compiler): add failing test for providing list to non-list type * Use name from ExtendedType where possible * fix(compiler): reject list values provided to non-list types * port value tests from graphql-js * only instantiate the test schema once * Use #[track_caller] Co-authored-by: Simon Sapin <[email protected]> * Use `ExtendedType::is_input_type` instead of manual match * Assert diagnostics output for the graphql-js value type tests * remove excess whitespace from test schema and queries Used the runtime `unindent` crate instead of compile-time `indoc` so it doesn't need to be repeated at every call site. * Remove outdated comment --------- Co-authored-by: Simon Sapin <[email protected]>
1 parent ac7b5f2 commit f1debb1

15 files changed

+2310
-78
lines changed

crates/apollo-compiler/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,9 @@ criterion = "0.5.1"
3939
expect-test = "1.4"
4040
notify = "6.0.0"
4141
pretty_assertions = "1.3.0"
42-
serial_test = "2.0.0"
4342
serde_json = "1.0"
43+
serial_test = "2.0.0"
44+
unindent = "0.2.3"
4445

4546
[[bench]]
4647
name = "multi-source"

crates/apollo-compiler/src/ast/impls.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -587,6 +587,23 @@ impl Type {
587587
Type::List(Box::new(self))
588588
}
589589

590+
/// If the type is a list type or a non-null list type, return the item type.
591+
///
592+
/// # Example
593+
/// ```
594+
/// use apollo_compiler::ty;
595+
/// // Returns the inner type of the list.
596+
/// assert_eq!(ty!([Foo!]).item_type(), &ty!(Foo!));
597+
/// // Not a list: returns the input.
598+
/// assert_eq!(ty!(Foo!).item_type(), &ty!(Foo!));
599+
/// ```
600+
pub fn item_type(&self) -> &Self {
601+
match self {
602+
Type::List(inner) | Type::NonNullList(inner) => inner,
603+
ty => ty,
604+
}
605+
}
606+
590607
/// Returns the inner named type, after unwrapping any non-null or list markers.
591608
pub fn inner_named_type(&self) -> &NamedType {
592609
match self {

crates/apollo-compiler/src/diagnostics.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ pub(crate) enum DiagnosticData {
180180
/// The source location where the directive that's being used was defined.
181181
directive_def: Option<NodeLocation>,
182182
},
183-
#[error("{ty} cannot be represented by a {value} value")]
183+
#[error("expected value of type {ty}, found {value}")]
184184
UnsupportedValueType {
185185
// input value
186186
value: String,

crates/apollo-compiler/src/validation/directive.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ pub(crate) fn validate_directives<'dir>(
280280
diagnostics.push(diag)
281281
} else {
282282
let type_diags =
283-
super::value::validate_values2(db, &input_value.ty, argument, var_defs);
283+
super::value::validate_values(db, &input_value.ty, argument, var_defs);
284284

285285
diagnostics.extend(type_diags);
286286
}

crates/apollo-compiler/src/validation/field.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ pub(crate) fn validate_field(
5454
{
5555
diagnostics.push(diag)
5656
} else {
57-
diagnostics.extend(super::value::validate_values2(
57+
diagnostics.extend(super::value::validate_values(
5858
db,
5959
&arg_definition.ty,
6060
argument,

crates/apollo-compiler/src/validation/value.rs

Lines changed: 34 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -36,21 +36,18 @@ fn unsupported_type(
3636
))
3737
}
3838

39-
//for bigint
40-
/*
41-
*/
42-
pub(crate) fn validate_values2(
39+
pub(crate) fn validate_values(
4340
db: &dyn ValidationDatabase,
4441
ty: &Node<ast::Type>,
4542
argument: &Node<ast::Argument>,
4643
var_defs: &[Node<ast::VariableDefinition>],
4744
) -> Vec<ApolloDiagnostic> {
4845
let mut diagnostics = vec![];
49-
value_of_correct_type2(db, ty, &argument.value, var_defs, &mut diagnostics);
46+
value_of_correct_type(db, ty, &argument.value, var_defs, &mut diagnostics);
5047
diagnostics
5148
}
5249

53-
pub(crate) fn value_of_correct_type2(
50+
pub(crate) fn value_of_correct_type(
5451
db: &dyn ValidationDatabase,
5552
ty: &Node<ast::Type>,
5653
arg_value: &Node<ast::Value>,
@@ -72,8 +69,10 @@ pub(crate) fn value_of_correct_type2(
7269
// When expected as an input type, any string (such as "4") or
7370
// integer (such as 4 or -4) input value should be coerced to ID
7471
ast::Value::Int(int) => match &type_definition {
72+
// Any value is valid for a custom scalar.
7573
schema::ExtendedType::Scalar(scalar) if !scalar.is_built_in() => {}
76-
schema::ExtendedType::Scalar(_) => match ty.inner_named_type().as_str() {
74+
schema::ExtendedType::Scalar(scalar) => match scalar.name.as_str() {
75+
// Any integer sequence is valid for an ID.
7776
"ID" => {}
7877
"Int" => {
7978
if int.try_to_i32().is_err() {
@@ -118,8 +117,9 @@ pub(crate) fn value_of_correct_type2(
118117
// with numeric content, must raise a request error indicating an
119118
// incorrect type.
120119
ast::Value::Float(float) => match &type_definition {
120+
// Any value is valid for a custom scalar.
121121
schema::ExtendedType::Scalar(scalar) if !scalar.is_built_in() => {}
122-
schema::ExtendedType::Scalar(_) if ty.inner_named_type() == "Float" => {
122+
schema::ExtendedType::Scalar(scalar) if scalar.name == "Float" => {
123123
if float.try_to_f64().is_err() {
124124
diagnostics.push(
125125
ApolloDiagnostic::new(
@@ -149,9 +149,7 @@ pub(crate) fn value_of_correct_type2(
149149
// booleans.
150150
// string, ids and custom scalars are ok, and
151151
// don't need a diagnostic.
152-
if scalar.is_built_in()
153-
&& !matches!(ty.inner_named_type().as_str(), "String" | "ID")
154-
{
152+
if scalar.is_built_in() && !matches!(scalar.name.as_str(), "String" | "ID") {
155153
diagnostics.push(unsupported_type(db, arg_value, ty));
156154
}
157155
}
@@ -162,17 +160,14 @@ pub(crate) fn value_of_correct_type2(
162160
// indicating an incorrect type.
163161
ast::Value::Boolean(_) => match &type_definition {
164162
schema::ExtendedType::Scalar(scalar) => {
165-
if scalar.is_built_in() && ty.inner_named_type().as_str() != "Boolean" {
163+
if scalar.is_built_in() && scalar.name.as_str() != "Boolean" {
166164
diagnostics.push(unsupported_type(db, arg_value, ty));
167165
}
168166
}
169167
_ => diagnostics.push(unsupported_type(db, arg_value, ty)),
170168
},
171169
ast::Value::Null => {
172-
if !matches!(
173-
type_definition,
174-
schema::ExtendedType::Enum(_) | schema::ExtendedType::Scalar(_)
175-
) {
170+
if ty.is_non_null() {
176171
diagnostics.push(unsupported_type(db, arg_value, ty));
177172
}
178173
}
@@ -191,7 +186,7 @@ pub(crate) fn value_of_correct_type2(
191186
if var_def.ty.is_non_null() && default_value.is_null() {
192187
diagnostics.push(unsupported_type(db, default_value, &var_def.ty))
193188
} else {
194-
value_of_correct_type2(
189+
value_of_correct_type(
195190
db,
196191
&var_def.ty,
197192
default_value,
@@ -204,9 +199,6 @@ pub(crate) fn value_of_correct_type2(
204199
}
205200
_ => diagnostics.push(unsupported_type(db, arg_value, ty)),
206201
},
207-
// GraphQL has a constant literal to represent enum input values.
208-
// GraphQL string literals must not be accepted as an enum input and
209-
// instead raise a request error.
210202
ast::Value::Enum(value) => match &type_definition {
211203
schema::ExtendedType::Enum(enum_) => {
212204
if !enum_.values.contains_key(value) {
@@ -216,12 +208,12 @@ pub(crate) fn value_of_correct_type2(
216208
value.location(),
217209
DiagnosticData::UndefinedValue {
218210
value: value.to_string(),
219-
definition: ty.inner_named_type().to_string(),
211+
definition: enum_.name.to_string(),
220212
},
221213
)
222214
.label(Label::new(
223215
arg_value.location(),
224-
format!("does not exist on `{}` type", ty.inner_named_type()),
216+
format!("does not exist on `{}` type", enum_.name),
225217
)),
226218
);
227219
}
@@ -236,14 +228,23 @@ pub(crate) fn value_of_correct_type2(
236228
// of size one, where the single item value is the result of input
237229
// coercion for the list’s item type on the provided value (note
238230
// this may apply recursively for nested lists).
239-
ast::Value::List(li) => match &type_definition {
240-
schema::ExtendedType::Scalar(_)
241-
| schema::ExtendedType::Enum(_)
242-
| schema::ExtendedType::InputObject(_) => li
243-
.iter()
244-
.for_each(|v| value_of_correct_type2(db, ty, v, var_defs, diagnostics)),
245-
_ => diagnostics.push(unsupported_type(db, arg_value, ty)),
246-
},
231+
ast::Value::List(li) => {
232+
let accepts_list = ty.is_list()
233+
// A named type can still accept a list if it is a custom scalar.
234+
|| matches!(type_definition, schema::ExtendedType::Scalar(scalar) if !scalar.is_built_in());
235+
if !accepts_list {
236+
diagnostics.push(unsupported_type(db, arg_value, ty))
237+
} else {
238+
let item_type = ty.same_location(ty.item_type().clone());
239+
if type_definition.is_input_type() {
240+
for v in li {
241+
value_of_correct_type(db, &item_type, v, var_defs, diagnostics);
242+
}
243+
} else {
244+
diagnostics.push(unsupported_type(db, arg_value, &item_type));
245+
}
246+
}
247+
}
247248
ast::Value::Object(obj) => match &type_definition {
248249
schema::ExtendedType::Scalar(scalar) if !scalar.is_built_in() => (),
249250
schema::ExtendedType::InputObject(input_obj) => {
@@ -260,12 +261,12 @@ pub(crate) fn value_of_correct_type2(
260261
value.location(),
261262
DiagnosticData::UndefinedValue {
262263
value: name.to_string(),
263-
definition: ty.inner_named_type().to_string(),
264+
definition: input_obj.name.to_string(),
264265
},
265266
)
266267
.label(Label::new(
267268
value.location(),
268-
format!("does not exist on `{}` type", ty.inner_named_type()),
269+
format!("does not exist on `{}` type", input_obj.name),
269270
)),
270271
);
271272
}
@@ -302,7 +303,7 @@ pub(crate) fn value_of_correct_type2(
302303
let used_val = obj.iter().find(|(obj_name, ..)| obj_name == input_name);
303304

304305
if let Some((_, v)) = used_val {
305-
value_of_correct_type2(db, ty, v, var_defs, diagnostics);
306+
value_of_correct_type(db, ty, v, var_defs, diagnostics);
306307
}
307308
})
308309
}

0 commit comments

Comments
 (0)