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

Implement rules for types. #5

Merged
merged 16 commits into from
Jan 2, 2024
Merged

Implement rules for types. #5

merged 16 commits into from
Jan 2, 2024

Conversation

nylvon
Copy link
Owner

@nylvon nylvon commented Dec 10, 2023

In order for easy and flexible type checking to take place, we need some nice types that would let this happen.

Type checking a type requires checking its internals for certain wanted behavior. Each internal check will be called a "rule", and the entire type check will be called a "rule set".

For instance, take this type:

const sampleType = struct {
    a: i32,
    b: []const u8,
};

Checking whether there is a member of name a inside sampleType would be done by creating a rule that can check for a member of name a inside any type, given a certain look-up mode (Either look up anything, or just fields or just declarations), and then using it on sampleType.

Analogous to checking whether a member of name a exists inside sampleType, we could make a rule that could check whether any type has a field named b that is also const and then use it on sampleType.

All these queries are atomic, but it would be a nice idea to let users make their own custom rules, as long as their own rules are checked so that they are valid custom type rule definitions, with valid custom rule descriptors.

Each Rule is defined and obeys an interface (for now it is not checked, as type checking is not implemented; afterwards all rules will check themselves).

Any type that checks the rule interface must have a Check function with the specified function signature below:

pub fn Check(comptime self: @This(), comptime target: type) !bool { ... }

Any custom rule is defined by a custom rule descriptor type (Which obeys the Rule interface mentioned earlier), and another Check function. This wrapper Check function inside the custom rule (NOT the descriptor!) first checks that the custom rule descriptor itself is a valid rule, and then safely calls the Check function from inside of the custom rule descriptor.

Things remaining to do:

@nylvon nylvon added documentation Improvements or additions to documentation enhancement New feature or request new core feature Adds a new core feature. labels Dec 10, 2023
@nylvon nylvon self-assigned this Dec 10, 2023
@nylvon nylvon linked an issue Dec 10, 2023 that may be closed by this pull request
9 tasks
@nylvon nylvon marked this pull request as draft December 10, 2023 13:38
@nylvon nylvon mentioned this pull request Dec 10, 2023
9 tasks
@nylvon
Copy link
Owner Author

nylvon commented Jan 2, 2024

Most of this is now done. Simple interfaces can be defined with a fluent syntax, for example:

const IsVec3Simple =
    Blank()
        .And(IsInType(FindField("x")))
        .And(IsInType(FindField("y")))
        .And(IsInType(FindField("z")));

will define a ruleset that will validate any type that has three fields "x", "y", "z" in them.

Then we can add the following ruleset:

const IsVec3Alternative =
    Blank()
        .And(IsInType(FindField("buffer")));

(And probably expand on this rule-set with more rules. like checking the dimension to be "3").

We could use either, and we could try to check every combination, or we could define a rule-set based off of them, like this:

const IsVec3Full =
    Blank()
        .Or(IsVec3Simple)
        .Or(IsVec3Alternative);

This will verify if it either has 3 components, or an internal buffer where they are stored. We may require this so that the type itself doesn't require more memory than we want it to. Sure, a vec3 could also be implemented with 60k linked lists that somehow intermingle and result in a vec3 behavior, but we may want a simple vec3 that is implemented like this and not that abstract.

This is nice, but then we would like to check if it has accessor methods, because we don't know (nor care) for the actual internal structure, so we'd define this:

const IsVec3Accessors =
    Blank()
        .And(IsFunction(FindDeclaration("get_X")))
        .And(IsFunction(FindDeclaration("get_Y")))
        .And(IsFunction(FindDeclaration("get_Z")));

Now we'd have some (rudimentary) accessor checking.

Then we could combine them to get our full IsVec3 rule-set (or, trait):

const IsVec3 = 
    Blank()
        .And(IsVec3Full)
        .And(IsVec3Accessors);

And then we could check any type like so:

pub fn ComplexVec3Op(arg: anytype) !void {
    comptime try IsVec3.Check(arg);
    
    // ... do something with arg ...
    // ... knowing it's a vec3 ...

}

All these work so far, so it's safe to merge this.
More tests will be required, and we need some more tools to make more complex patterns possible, but so far it's working and one can define interfaces like this.

They're all at compile-time, and are therefore zero-cost (well, zero runtime cost, of course this will increase the compilation time, by how much I cannot judge right now, we'll see as this develops).

@nylvon nylvon marked this pull request as ready for review January 2, 2024 18:35
@nylvon nylvon merged commit 21fa8cf into main Jan 2, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation enhancement New feature or request new core feature Adds a new core feature.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Implement Base Functionality
1 participant