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

feat: add support for the ..= RangeInclusive syntax #6972

Open
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

cairoIover
Copy link
Contributor

Adds support for the DotDotEq token in the parser, link BinaryOperator::DotDotEq(_) to RangeInclusiveOp and implement it in the core library.

@reviewable-StarkWare
Copy link

This change is Reviewable

@cairoIover cairoIover force-pushed the feat/range-inclusive-op branch 2 times, most recently from 17331b6 to ee47ba0 Compare January 4, 2025 02:53
Copy link
Collaborator

@orizi orizi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 5 of 16 files at r1, 3 of 4 files at r2.
Reviewable status: 8 of 16 files reviewed, 6 unresolved discussions (waiting on @cairoIover)


corelib/src/ops.cairo line 16 at r2 (raw file):

// `RangeOp` is used internally by the compiler.
#[allow(unused_imports)]
use range::RangeOp;

Suggestion:

// `RangeOp` and `RangeInclusiveOp` are used internally by the compiler.
#[allow(unused_imports)]
use range::{RangeOp, RangeInclusiveOp};

crates/cairo-lang-parser/src/lexer.rs line 282 at r2 (raw file):

                                _ => TokenKind::DotDot,
                            }
                        }

Suggestion:

                        Some('.') => self.pick_kind('=', TokenKind::DotDotEq, TokenKind::DotDot),

corelib/src/ops/range.cairo line 145 at r2 (raw file):

        }
    }
}

Suggestion:

impl RangeInclusiveIteratorImpl<
    T, +One<T>, +Add<T>, +Copy<T>, +Drop<T>, +PartialEq<T>, +PartialOrd<T>,
> of Iterator<RangeInclusiveIterator<T>> {
    type Item = T;

    fn next(ref self: RangeInclusiveIterator<T>) -> Option<T> {
        let next = self.cur + One::one();
        if next != self.end {
            let value = self.cur;
            self.cur = next;
            Option::Some(value)
        } else {
            Option::None
        }
    }
}

corelib/src/ops/range.cairo line 167 at r2 (raw file):

        }
    }
}

Suggestion:

pub impl RangeInclusiveIntoIterator<
    T,
    +One<T>,
    +Add<T>,
    +Copy<T>,
    +Drop<T>,
    +PartialEq<T>,
    +PartialOrd<T>,
    -SierraIntRangeSupport<T>,
> of IntoIterator<RangeInclusive<T>> {
    type IntoIter = RangeInclusiveIterator<T>;

    fn into_iter(self: RangeInclusive<T>) -> Self::IntoIter {
        if self.start <= self.end {
            Self::IntoIter { cur: self.start, end: self.end }
        } else {
            // Since `self.end < self.start` this will never overflow.
            let after_end = self.end + One::one();
            Self::IntoIter { cur: after_end, end: self.end }
        }
    }
}

corelib/src/test/range_test.cairo line 23 at r2 (raw file):

    assert!(iter.next() == Option::Some(3));
    assert!(iter.next() == Option::None);
}

Suggestion:

#[test]
fn test_range_inclusive_iterator() {
    let mut iter = (1_usize..=3).into_iter();
    assert!(iter.next() == Option::Some(1));
    assert!(iter.next() == Option::Some(2));
    assert!(iter.next() == Option::Some(3));
    assert!(iter.next() == Option::None);
}

#[test]
fn test_range_inclusive_iterator_range_end() {
    let mut iter = (253_u8..=255).into_iter();
    assert!(iter.next() == Option::Some(253));
    assert!(iter.next() == Option::Some(254));
    assert!(iter.next() == Option::Some(255));
    assert!(iter.next() == Option::None);
}

#[test]
fn test_range_inclusive_empty_ranges() {
    let mut iter = (255_u8..=125).into_iter();
    assert!(iter.next() == Option::None);
    let mut iter = (255_u8..=0).into_iter();
    assert!(iter.next() == Option::None);
}

crates/cairo-lang-syntax/src/node/key_fields.rs line 2 at r2 (raw file):

// Autogenerated file. To regenerate, please run `cargo run --bin generate-syntax`.

use the same rust_fmt.

Copy link
Contributor Author

@cairoIover cairoIover left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: 8 of 16 files reviewed, 6 unresolved discussions (waiting on @orizi)


crates/cairo-lang-syntax/src/node/key_fields.rs line 2 at r2 (raw file):

Previously, orizi wrote…

use the same rust_fmt.

this is what running cargo run --bin generate-syntax gets me 🤔


corelib/src/ops/range.cairo line 145 at r2 (raw file):

        }
    }
}

I don't believe this logic to be valid; counter examples:
1.
starting state: cur = 10, end=11 => next=11 => next == end => return Option::None when this should be returning Some(11)

I am proposing something else; see new code with a boolean flag for exhaustion.


corelib/src/ops/range.cairo line 167 at r2 (raw file):

        }
    }
}

I think this could overflow on integer bounds. see new proposal


corelib/src/test/range_test.cairo line 23 at r2 (raw file):

    assert!(iter.next() == Option::Some(3));
    assert!(iter.next() == Option::None);
}

Done.


corelib/src/ops.cairo line 16 at r2 (raw file):

// `RangeOp` is used internally by the compiler.
#[allow(unused_imports)]
use range::RangeOp;

Done.


crates/cairo-lang-parser/src/lexer.rs line 282 at r2 (raw file):

                                _ => TokenKind::DotDot,
                            }
                        }

Done.

Copy link
Collaborator

@orizi orizi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 4 of 16 files at r1, 6 of 6 files at r3, all commit messages.
Reviewable status: all files reviewed, 1 unresolved discussion (waiting on @cairoIover)


a discussion (no related file):
@gilbens-starkware @TomerStarkware @dean-starkware multiple second eyes.

Copy link
Collaborator

@dean-starkware dean-starkware left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: all files reviewed, 5 unresolved discussions (waiting on @cairoIover)


corelib/src/ops/range.cairo line 115 at r3 (raw file):

#[derive(Clone, Drop)]
pub struct RangeInclusiveIterator<T> {

T: PartialOrd + Clone

Code quote:

T

corelib/src/ops/range.cairo line 133 at r3 (raw file):

pub impl RangeInclusiveOpImpl<T> of RangeInclusiveOp<T> {
    /// Handles the `..=` operator. Returns the value of the expression `start..=end`.
    fn range_inclusive(start: T, end: T) -> RangeInclusive<T> {

assert!(start <= end, "start must be less than or equal to end");


corelib/src/ops/range.cairo line 153 at r3 (raw file):

    fn next(ref self: RangeInclusiveIterator<T>) -> Option<T> {
        if self.exhausted {

Instead of using a manual exhausted flag, consider leveraging the range bounds directly (e.g., self.cur > self.end).
If self.cur or self.end is mutated outside this struct (e.g., via unsafe code or direct access), the exhausted flag may no longer correctly represent the iterator's state.


corelib/src/ops/range.cairo line 184 at r3 (raw file):

    fn into_iter(self: RangeInclusive<T>) -> Self::IntoIter {
        let exhausted = self.start > self.end;

For non-primitive types, this comparison might be expensive.
Maybe try to cache the result of this check during construction if it is used frequently?

Copy link
Contributor Author

@cairoIover cairoIover left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: all files reviewed, 5 unresolved discussions (waiting on @dean-starkware)


corelib/src/ops/range.cairo line 115 at r3 (raw file):

Previously, dean-starkware wrote…

T: PartialOrd + Clone

not sure I understand what you mean here


corelib/src/ops/range.cairo line 133 at r3 (raw file):

Previously, dean-starkware wrote…

assert!(start <= end, "start must be less than or equal to end");

I don't think the operator should prevent you from building such a range. you can do this with Range to.


corelib/src/ops/range.cairo line 153 at r3 (raw file):

self.cur > self.end

I cannot do that as if self.end is T::MAX then self.cur cannot be incremented by one due to overflow issues

If self.cur or self.end is mutated outside this struct (e.g., via unsafe code or direct access), the exhausted flag may no longer correctly represent the iterator's state.

the fields are private


corelib/src/ops/range.cairo line 184 at r3 (raw file):

Previously, dean-starkware wrote…

For non-primitive types, this comparison might be expensive.
Maybe try to cache the result of this check during construction if it is used frequently?

This is only done once during initialization of the iterator. I don't see where this could be cached

Copy link
Collaborator

@dean-starkware dean-starkware left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: all files reviewed, 3 unresolved discussions (waiting on @cairoIover)


corelib/src/ops/range.cairo line 115 at r3 (raw file):

Previously, cairoIover wrote…

not sure I understand what you mean here

While RangeInclusive is generic over T, the implementation assumes that T supports certain operations (PartialOrd, Clone, etc.). These bounds are not enforced in the struct definition.
If T is not comparable (PartialOrd) or cannot be cloned (Clone), this code will fail to compile when used.


corelib/src/ops/range.cairo line 133 at r3 (raw file):

Previously, cairoIover wrote…

I don't think the operator should prevent you from building such a range. you can do this with Range to.

If start > end, the resulting range behaves as an empty range. This behavior doesn't make any sense in my opinion.

Copy link
Contributor Author

@cairoIover cairoIover left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: all files reviewed, 3 unresolved discussions (waiting on @dean-starkware)


corelib/src/ops/range.cairo line 115 at r3 (raw file):

Previously, dean-starkware wrote…

While RangeInclusive is generic over T, the implementation assumes that T supports certain operations (PartialOrd, Clone, etc.). These bounds are not enforced in the struct definition.
If T is not comparable (PartialOrd) or cannot be cloned (Clone), this code will fail to compile when used.

You cannot enforce bounds in struct definitions in Cairo. If you declare

#[derive(Drop, Clone)]
struct S{}

Then S cannot be instanciated with instances of T that do not derive Drop nor Clone

#[derive(Drop, Clone)]
struct S<T: ParialOrd + Clone>{}

is not a valid syntax


corelib/src/ops/range.cairo line 133 at r3 (raw file):

Previously, dean-starkware wrote…

If start > end, the resulting range behaves as an empty range. This behavior doesn't make any sense in my opinion.

In my opinion, it does make sense. We do not want to trigger runtime errors in such behaviors.

An empty range is not inherently wrong or dangerous. It's just empty.
In set theory, the interval [5,3] contains all the values between 5 and 3. Which means that [5, 3] = {}

Moreover, panicking if start <= end is inconsistent with the behavior of most programming languages, like rust or python.

Copy link
Collaborator

@dean-starkware dean-starkware left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 9 of 16 files at r1, 1 of 4 files at r2, 6 of 6 files at r3, all commit messages.
Reviewable status: all files reviewed, 1 unresolved discussion (waiting on @cairoIover)

Copy link
Contributor

@gilbens-starkware gilbens-starkware left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: all files reviewed, 2 unresolved discussions (waiting on @cairoIover)


corelib/src/ops/range.cairo line 179 at r3 (raw file):

    +PartialEq<T>,
    +PartialOrd<T>,
    -SierraIntRangeSupport<T>,

Remove this, as this won't be relevant for inclusive range.

Code quote:

-SierraIntRangeSupport<T>,

@cairoIover cairoIover force-pushed the feat/range-inclusive-op branch from 5d2c040 to f6e94ca Compare January 9, 2025 13:18
Copy link
Contributor Author

@cairoIover cairoIover left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: 6 of 16 files reviewed, 2 unresolved discussions (waiting on @dean-starkware, @gilbens-starkware, and @orizi)


corelib/src/ops/range.cairo line 179 at r3 (raw file):

Previously, gilbens-starkware (Gil Ben-Shachar) wrote…

Remove this, as this won't be relevant for inclusive range.

Done.

Copy link
Contributor

@gilbens-starkware gilbens-starkware left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 4 of 16 files at r1, 1 of 4 files at r2, 1 of 6 files at r3, 9 of 10 files at r4, all commit messages.
Reviewable status: 15 of 16 files reviewed, 1 unresolved discussion (waiting on @cairoIover, @dean-starkware, and @orizi)

Copy link
Contributor

@gilbens-starkware gilbens-starkware left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:lgtm:

Reviewable status: 15 of 16 files reviewed, 1 unresolved discussion (waiting on @cairoIover, @dean-starkware, and @orizi)

Copy link
Collaborator

@orizi orizi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 1 of 10 files at r4, all commit messages.
Reviewable status: :shipit: complete! all files reviewed, all discussions resolved (waiting on @cairoIover)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants