Skip to content
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

represent parameterised grammar (const values) #538

Closed
Tracked by #641
lrlna opened this issue Apr 26, 2023 · 9 comments
Closed
Tracked by #641

represent parameterised grammar (const values) #538

lrlna opened this issue Apr 26, 2023 · 9 comments
Labels
apollo-compiler issues/PRs pertaining to semantic analysis & validation apollo-parser bug Something isn't working

Comments

@lrlna
Copy link
Member

lrlna commented Apr 26, 2023

We don't correctly diagnose incorrectly parameterised values on the parser side of things. For example, Directive in a VariableDefinition must not have a Variable as its Argument's Value. spec. We should produce this diagnostic in the parser.

Perhaps the most straightforward way to do this is to have two versions of the the Value enum - one with Variable and the other one without, and then propagate that up to Arguments, Argument, Directives and Directive.

Would be useful to have that enum variant type representation in rust here, but alas not possible in the language yet :/

@lrlna lrlna added bug Something isn't working apollo-parser labels Apr 26, 2023
@SimonSapin SimonSapin added the apollo-compiler issues/PRs pertaining to semantic analysis & validation label Nov 3, 2023
@SimonSapin
Copy link
Contributor

Would it be worth making const-ness a type parameter?

enum Value<C: Constness> {
    Null,
    Boolean(bool),
    // …
    Variable(C::Variable),
}

trait Constness {
    type Variable;
}

struct NotConst;
impl Constness for NotConst {
    type Variable = Name;
}

struct Const;
impl ConstNess for Const {
    type Variable = Never;
}
enum Never {}

Or without the indirection:

enum MaybeConstValue<Variable> {
    Null,
    Boolean(bool),
    // …
    Variable(Variable),
}

type Value = MaybeConstValue<Name>;
type ConstValue = MaybeConstValue<Never>;
enum Never {}

@goto-bus-stop
Copy link
Member

goto-bus-stop commented Nov 9, 2023

With the Never pattern you don't need to match on ConstValue::Variable(_) right?

The constness has to propagate through a few levels (Directives -> Arguments -> Value), and users will have to write functions that are generic over constness. So I think having a clearly named Constness trait that they can use as a bound is a good idea.

Maybe an ergonomic consideration: if you have a function that operates on a not-const value you can't put a const value in unless you make it generic over constness or use a conversion function.

@SimonSapin
Copy link
Contributor

I tried it, with Never you’d need a match arm like this:

ConstValue::Variable(never) => match never {}

… which type-checks for any expected type. "All" of the zero arms of the inner match for each variant of Never have a value of the expected type.

It’s only with the unstable ! type that the Variable match arm could be skipped entirely.

@SimonSapin
Copy link
Contributor

To avoid zero-variant-enum weirdness, another possibility is separate Value and ConstValue enums. Then Argument, Directive, etc can be generic over the value type.

@goto-bus-stop
Copy link
Member

Both have tradeoffs, you can't generically match on two unrelated enum types, but the incantation you have to do for the never branch is also not great. Maybe the former isn't such a problem if you can easily convert ConstValue to Value and just use that whenever you want to handle both kinds of values.

@SimonSapin
Copy link
Contributor

#777 fixed the parser to emit errors for variable in const values. I’ve opened #852 to do the same in validation.

This leaves for this issue the possibility of encoding value constness in the Rust type system, which is not a bug fix but a design decision. Based on the experience that two separate DirectiveList types are inconvenient, I’m inclined to not have two entirely separate Value and ConstValue enums.

This leaves the possibility of a generic enum, but I’m not sure it’s worth the complexity. The status quo is not so bad:

match value_expected_to_be_const {
    Value::Variable(_) => unreachable!()
    // Handle other cases
}

With #852, if the value is from a Valid<_> document we can be reasonably confident that code like the above won’t panic.

@SimonSapin
Copy link
Contributor

With the Never pattern you don't need to match on ConstValue::Variable(_) right?

You don’t anymore! (Though Router is stuck on an older toolchain right now) https://blog.rust-lang.org/2024/10/17/Rust-1.82.0.html#omitting-empty-types-in-pattern-matching

@goto-bus-stop
Copy link
Member

that does help!

i'm not opposed to using a generic enum, but i'm also pretty happy with the status quo (+ #852).

@goto-bus-stop goto-bus-stop removed their assignment Oct 31, 2024
@SimonSapin
Copy link
Contributor

#777 and #925 ensure that variable values used in const contexts are treated as errors during parsing and validation. Making them further unrepresentable in the Rust data structure would be possible but probably not worth the API complexity, so we’ve decided to stick with the current representation for apollo-compiler 1.0: Value is a single Rust enum that always includes a Variable variant, even where using that variant would be invalid.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
apollo-compiler issues/PRs pertaining to semantic analysis & validation apollo-parser bug Something isn't working
Projects
None yet
Development

No branches or pull requests

4 participants