Skip to content

Remove DynType and resculpt ast::Type #1322

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Open
8 changes: 7 additions & 1 deletion juniper/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
- `ast::Type`:
- Removed lifetime parameters.
- Made it generic over string type.
- Remade as a struct with methods: ([#1322])
- Added `modifier()` and `modifiers()` methods returning `TypeModifier`.
- Added `is_list()` method.
- Added `wrap_list()` and `wrap_non_null() methods.
- Added `nullable()` constructor.
- Added `BorrowedType` representation.
- `MetaType`:
- Removed lifetime parameters.
- Made `name()`, `description()` and `specified_by_url()` methods returning `ArcStr`.
Expand All @@ -52,7 +58,6 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
- Made `name` and `description` fields using `ArcStr`.
- `SchemaType`:
- Removed lifetime parameters.
- Made `is_subtype()` method accepting `DynType` instead of `Type`.
- `RootNode`:
- Removed lifetime parameters.
- `Registry`:
Expand Down Expand Up @@ -103,6 +108,7 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
[#1293]: /../../pull/1293
[#1311]: /../../pull/1311
[#1318]: /../../pull/1318
[#1322]: /../../pull/1322
[#1323]: /../../pull/1323
[1b1fc618]: /../../commit/1b1fc61879ffdd640d741e187dc20678bf7ab295
[20609366]: /../../commit/2060936635609b0186d46d8fbd06eb30fce660e3
Expand Down
1 change: 1 addition & 0 deletions juniper/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ rust_decimal = { version = "1.20", default-features = false, optional = true }
ryu = { version = "1.0", optional = true }
serde = { version = "1.0.122", features = ["derive"] }
serde_json = { version = "1.0.18", features = ["std"], default-features = false, optional = true }
smallvec = "1.15"
static_assertions = "1.1"
time = { version = "0.3.37", features = ["formatting", "macros", "parsing"], optional = true }
url = { version = "2.0", optional = true }
Expand Down
217 changes: 179 additions & 38 deletions juniper/src/ast.rs
Original file line number Diff line number Diff line change
@@ -1,78 +1,219 @@
use std::{borrow::Cow, fmt, hash::Hash, slice, vec};

use arcstr::ArcStr;

use indexmap::IndexMap;
use smallvec::SmallVec;

#[cfg(doc)]
use self::TypeModifier::{List, NonNull};
use crate::{
executor::Variables,
parser::Spanning,
value::{DefaultScalarValue, ScalarValue},
};

/// Possible modifiers in a [`Type`] literal.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum TypeModifier {
/// Non-`null` type (e.g. `<type>!`).
NonNull,

/// List of types (e.g. `[<type>]`).
List(Option<usize>),
}

/// Type literal in a syntax tree.
///
/// This enum carries no semantic information and might refer to types that do not exist.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Type<N = ArcStr> {
/// `null`able named type, e.g. `String`.
Named(N),
/// Carries no semantic information and might refer to types that don't exist.
#[derive(Clone, Copy, Debug)]
pub struct Type<N = ArcStr, M = SmallVec<[TypeModifier; 2]>> {
/// Name of this [`Type`].
name: N,

/// `null`able list type, e.g. `[String]`.
/// Modifiers of this [`Type`].
///
/// The list itself is `null`able, the containing [`Type`] might be non-`null`.
List(Box<Type<N>>, Option<usize>),
/// The first one is the innermost one.
modifiers: M,
}

/// Non-`null` named type, e.g. `String!`.
NonNullNamed(N),
impl<N, M> Eq for Type<N, M> where Self: PartialEq {}

/// Non-`null` list type, e.g. `[String]!`.
///
/// The list itself is non-`null`, the containing [`Type`] might be `null`able.
NonNullList(Box<Type<N>>, Option<usize>),
impl<N1, N2, M1, M2> PartialEq<Type<N2, M2>> for Type<N1, M1>
where
N1: AsRef<str>,
N2: AsRef<str>,
M1: AsRef<[TypeModifier]>,
M2: AsRef<[TypeModifier]>,
{
fn eq(&self, other: &Type<N2, M2>) -> bool {
self.name.as_ref() == other.name.as_ref()
&& self.modifiers.as_ref() == other.modifiers.as_ref()
}
}

impl<N: fmt::Display> fmt::Display for Type<N> {
impl<N, M> fmt::Display for Type<N, M>
where
N: AsRef<str>,
M: AsRef<[TypeModifier]>,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Named(n) => write!(f, "{n}"),
Self::NonNullNamed(n) => write!(f, "{n}!"),
Self::List(t, _) => write!(f, "[{t}]"),
Self::NonNullList(t, _) => write!(f, "[{t}]!"),
match self.modifier() {
Some(TypeModifier::NonNull) => write!(f, "{}!", self.borrow_inner()),
Some(TypeModifier::List(..)) => write!(f, "[{}]", self.borrow_inner()),
None => write!(f, "{}", self.name.as_ref()),
}
}
}

impl<N: AsRef<str>> Type<N> {
/// Returns the name of this named [`Type`].
impl<'a, N, M> From<&'a Type<N, M>> for BorrowedType<'a>
where
N: AsRef<str>,
M: AsRef<[TypeModifier]>,
{
fn from(value: &'a Type<N, M>) -> Self {
Self {
name: value.name.as_ref(),
modifiers: value.modifiers.as_ref(),
}
}
}

impl<N: AsRef<str>, M: AsRef<[TypeModifier]>> Type<N, M> {
/// Borrows the inner [`Type`] of this modified [`Type`], removing its topmost [`TypeModifier`].
///
/// # Panics
///
/// Only applies to named [`Type`]s. Lists will return [`None`].
/// If this [`Type`] has no [`TypeModifier`]s.
pub(crate) fn borrow_inner(&self) -> BorrowedType<'_> {
let modifiers = self.modifiers.as_ref();
match modifiers.len() {
0 => panic!("no inner `Type` available"),
n => Type {
name: self.name.as_ref(),
modifiers: &modifiers[..n - 1],
},
}
}

/// Returns the name of this [`Type`].
///
/// [`List`]s will return [`None`].
#[must_use]
pub fn name(&self) -> Option<&str> {
match self {
Self::Named(n) | Self::NonNullNamed(n) => Some(n.as_ref()),
Self::List(..) | Self::NonNullList(..) => None,
}
(!self.is_list()).then(|| self.name.as_ref())
}

/// Returns the innermost name of this [`Type`] by unpacking lists.
/// Returns the innermost name of this [`Type`] by unpacking [`List`]s.
///
/// All [`Type`] literals contain exactly one named type.
/// All [`Type`] literals contain exactly one name.
#[must_use]
pub fn innermost_name(&self) -> &str {
match self {
Self::Named(n) | Self::NonNullNamed(n) => n.as_ref(),
Self::List(l, ..) | Self::NonNullList(l, ..) => l.innermost_name(),
}
self.name.as_ref()
}

/// Returns the topmost [`TypeModifier`] of this [`Type`], if any.
#[must_use]
pub fn modifier(&self) -> Option<&TypeModifier> {
self.modifiers().last()
}

/// Returns [`TypeModifier`]s of this [`Type`], if any.
///
/// The first one is the innermost one.
#[must_use]
pub fn modifiers(&self) -> &[TypeModifier] {
self.modifiers.as_ref()
}

/// Indicates whether this [`Type`] can only represent non-`null` values.
/// Indicates whether this [`Type`] is [`NonNull`].
#[must_use]
pub fn is_non_null(&self) -> bool {
match self {
Self::NonNullList(..) | Self::NonNullNamed(..) => true,
Self::List(..) | Self::Named(..) => false,
match self.modifiers.as_ref().last() {
Some(TypeModifier::NonNull) => true,
Some(TypeModifier::List(..)) | None => false,
}
}

/// Indicates whether this [`Type`] represents a [`List`] (either `null`able or [`NonNull`]).
#[must_use]
pub fn is_list(&self) -> bool {
match self.modifiers.as_ref().last() {
Some(TypeModifier::NonNull) => self.borrow_inner().is_non_null(),
Some(TypeModifier::List(..)) => true,
None => false,
}
}
}

impl<N, M: Default> Type<N, M> {
/// Creates a new `null`able [`Type`] literal from the provided `name`.
#[must_use]
pub fn nullable(name: impl Into<N>) -> Self {
Self {
name: name.into(),
modifiers: M::default(),
}
}
}

impl<N, M: Extend<TypeModifier>> Type<N, M> {
/// Wraps this [`Type`] into the provided [`TypeModifier`].
fn wrap(mut self, modifier: TypeModifier) -> Self {
self.modifiers.extend([modifier]);
self
}

/// Wraps this [`Type`] into a [`List`] with the provided `expected_size`, if any.
#[must_use]
pub fn wrap_list(self, expected_size: Option<usize>) -> Self {
self.wrap(TypeModifier::List(expected_size))
}

/// Wraps this [`Type`] as a [`NonNull`] one.
#[must_use]
pub fn wrap_non_null(self) -> Self {
self.wrap(TypeModifier::NonNull)
}
}

impl<N: AsRef<str>> Type<N> {
/// Strips this [`Type`] from [`NonNull`], returning it as a `null`able one.
pub(crate) fn into_nullable(mut self) -> Self {
if self.is_non_null() {
_ = self.modifiers.pop();
}
self
}
}

/// Borrowed variant of a [`Type`] literal.
pub(crate) type BorrowedType<'a> = Type<&'a str, &'a [TypeModifier]>;

impl<'a> BorrowedType<'a> {
/// Creates a [`NonNull`] [`BorrowedType`] literal from the provided `name`.
pub(crate) fn non_null(name: &'a str) -> Self {
Self {
name,
modifiers: &[TypeModifier::NonNull],
}
}

/// Borrows the inner [`Type`] of this [`List`] [`Type`], if it represents one.
pub(crate) fn borrow_list_inner(&self) -> Option<Self> {
let mut out = None;
for (n, m) in self.modifiers.iter().enumerate().rev() {
match m {
TypeModifier::NonNull => {}
TypeModifier::List(..) => {
out = Some(Self {
name: self.name,
modifiers: &self.modifiers[..n],
});
break;
}
}
}
out
}
}

Expand Down
5 changes: 4 additions & 1 deletion juniper/src/executor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1155,7 +1155,10 @@ impl<S> Registry<S> {
if let Some(name) = T::name(info) {
let validated_name = Name::new(name.clone()).unwrap();
if !self.types.contains_key(&name) {
self.insert_placeholder(validated_name.clone(), Type::NonNullNamed(name.clone()));
self.insert_placeholder(
validated_name.clone(),
Type::nullable(name.clone()).wrap_non_null(),
);
let meta = T::meta(info, self);
self.types.insert(validated_name, meta);
}
Expand Down
20 changes: 7 additions & 13 deletions juniper/src/parser/document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -514,13 +514,9 @@ pub fn parse_type<'a>(parser: &mut Parser<'a>) -> ParseResult<Type<&'a str>> {
{
let inner_type = parse_type(parser)?;
let end_pos = parser.expect(&Token::BracketClose)?.span.end;
Spanning::start_end(
&start_span.start,
&end_pos,
Type::List(Box::new(inner_type.item), None),
)
Spanning::start_end(&start_span.start, &end_pos, inner_type.item.wrap_list(None))
} else {
parser.expect_name()?.map(Type::Named)
parser.expect_name()?.map(Type::nullable)
};

Ok(match *parser.peek() {
Expand All @@ -534,15 +530,13 @@ pub fn parse_type<'a>(parser: &mut Parser<'a>) -> ParseResult<Type<&'a str>> {

fn wrap_non_null<'a>(
parser: &mut Parser<'a>,
inner: Spanning<Type<&'a str>>,
mut inner: Spanning<Type<&'a str>>,
) -> ParseResult<Type<&'a str>> {
let end_pos = &parser.expect(&Token::ExclamationMark)?.span.end;

let wrapped = match inner.item {
Type::Named(name) => Type::NonNullNamed(name),
Type::List(l, expected_size) => Type::NonNullList(l, expected_size),
ty @ (Type::NonNullList(..) | Type::NonNullNamed(..)) => ty,
};
if !inner.item.is_non_null() {
inner.item = inner.item.wrap_non_null();
}

Ok(Spanning::start_end(&inner.span.start, end_pos, wrapped))
Ok(Spanning::start_end(&inner.span.start, end_pos, inner.item))
}
4 changes: 2 additions & 2 deletions juniper/src/parser/tests/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,8 +173,8 @@ fn input_value_literals() {
),
);
let fields = [
Argument::new("key", Type::NonNullNamed("Int".into())),
Argument::new("other", Type::NonNullNamed("Bar".into())),
Argument::new("key", Type::nullable("Int").wrap_non_null()),
Argument::new("other", Type::nullable("Bar").wrap_non_null()),
];
let meta = &MetaType::InputObject(InputObjectMeta::new::<Foo>("foo", &fields));
assert_eq!(
Expand Down
12 changes: 3 additions & 9 deletions juniper/src/schema/meta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -710,18 +710,12 @@ impl<S> MetaType<S> {
| Self::Interface(InterfaceMeta { name, .. })
| Self::Object(ObjectMeta { name, .. })
| Self::Scalar(ScalarMeta { name, .. })
| Self::Union(UnionMeta { name, .. }) => Type::NonNullNamed(name.clone()),
| Self::Union(UnionMeta { name, .. }) => Type::nullable(name.clone()).wrap_non_null(),
Self::List(ListMeta {
of_type,
expected_size,
}) => Type::NonNullList(Box::new(of_type.clone()), *expected_size),
Self::Nullable(NullableMeta { of_type }) => match of_type {
Type::NonNullNamed(inner) => Type::Named(inner.clone()),
Type::NonNullList(inner, expected_size) => {
Type::List(inner.clone(), *expected_size)
}
ty @ (Type::List(..) | Type::Named(..)) => ty.clone(),
},
}) => of_type.clone().wrap_list(*expected_size).wrap_non_null(),
Self::Nullable(NullableMeta { of_type }) => of_type.clone().into_nullable(),
Self::Placeholder(PlaceholderMeta { of_type }) => of_type.clone(),
}
}
Expand Down
Loading