solar_ast/ast/
semver.rs

1use crate::BoxSlice;
2use semver::Op;
3use solar_data_structures::smallvec::{SmallVec, smallvec};
4use solar_interface::Span;
5use std::{cmp::Ordering, fmt};
6
7pub use semver::Op as SemverOp;
8
9// We use the same approach as Solc.
10// See [`SemverReq::dis`] field docs for more details on how the requirements are treated.
11
12// Solc implementation notes:
13// - uses `unsigned` (`u32`) for version integers: https://github.com/argotorg/solidity/blob/e81f2bdbd66e9c8780f74b8a8d67b4dc2c87945e/liblangutil/SemVerHandler.cpp#L258
14// - version numbers can be `*/x/X`, which are represented as `u32::MAX`: https://github.com/argotorg/solidity/blob/e81f2bdbd66e9c8780f74b8a8d67b4dc2c87945e/liblangutil/SemVerHandler.cpp#L263
15// - ranges are parsed as `>=start, <=end`: https://github.com/argotorg/solidity/blob/e81f2bdbd66e9c8780f74b8a8d67b4dc2c87945e/liblangutil/SemVerHandler.cpp#L209
16//   we however dedicate a separate node for this: [`SemverReqComponentKind::Range`]
17
18/// A SemVer version number.
19#[derive(Clone, Copy)]
20pub enum SemverVersionNumber {
21    /// A number.
22    Number(u32),
23    /// `*`, `X`, or `x`.
24    Wildcard,
25}
26
27impl From<u64> for SemverVersionNumber {
28    #[inline]
29    fn from(n: u64) -> Self {
30        match n.try_into() {
31            Ok(n) => Self::Number(n),
32            Err(_) => Self::Wildcard,
33        }
34    }
35}
36
37impl From<SemverVersionNumber> for u64 {
38    #[inline]
39    fn from(value: SemverVersionNumber) -> Self {
40        match value {
41            SemverVersionNumber::Number(n) => n as Self,
42            SemverVersionNumber::Wildcard => Self::MAX,
43        }
44    }
45}
46
47impl fmt::Display for SemverVersionNumber {
48    #[inline]
49    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50        match self {
51            Self::Number(n) => n.fmt(f),
52            Self::Wildcard => "*".fmt(f),
53        }
54    }
55}
56
57impl fmt::Debug for SemverVersionNumber {
58    #[inline]
59    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60        fmt::Display::fmt(self, f)
61    }
62}
63
64impl PartialEq for SemverVersionNumber {
65    #[inline]
66    fn eq(&self, other: &Self) -> bool {
67        match (self, other) {
68            (Self::Wildcard, _) | (_, Self::Wildcard) => true,
69            (Self::Number(a), Self::Number(b)) => a == b,
70        }
71    }
72}
73
74impl Eq for SemverVersionNumber {}
75
76impl PartialOrd for SemverVersionNumber {
77    #[inline]
78    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
79        Some(self.cmp(other))
80    }
81}
82
83impl Ord for SemverVersionNumber {
84    #[inline]
85    fn cmp(&self, other: &Self) -> Ordering {
86        match (self, other) {
87            (Self::Wildcard, _) | (_, Self::Wildcard) => Ordering::Equal,
88            (Self::Number(a), Self::Number(b)) => a.cmp(b),
89        }
90    }
91}
92
93/// A SemVer version.
94#[derive(Clone)]
95pub struct SemverVersion {
96    pub span: Span,
97    /// Major version.
98    pub major: SemverVersionNumber,
99    /// Minor version. Optional.
100    pub minor: Option<SemverVersionNumber>,
101    /// Patch version. Optional.
102    pub patch: Option<SemverVersionNumber>,
103    // Pre-release and build metadata are not supported.
104}
105
106impl PartialEq for SemverVersion {
107    #[inline]
108    fn eq(&self, other: &Self) -> bool {
109        self.cmp(other) == Ordering::Equal
110    }
111}
112
113impl Eq for SemverVersion {}
114
115impl PartialOrd for SemverVersion {
116    #[inline]
117    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
118        Some(self.cmp(other))
119    }
120}
121
122impl Ord for SemverVersion {
123    #[inline]
124    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
125        #[inline]
126        fn cmp_opt(a: &Option<SemverVersionNumber>, b: &Option<SemverVersionNumber>) -> Ordering {
127            match (a, b) {
128                (Some(a), Some(b)) => a.cmp(b),
129                _ => Ordering::Equal,
130            }
131        }
132
133        self.major
134            .cmp(&other.major)
135            .then_with(|| cmp_opt(&self.minor, &other.minor))
136            .then_with(|| cmp_opt(&self.patch, &other.patch))
137    }
138}
139
140impl fmt::Display for SemverVersion {
141    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142        let Self { span: _, major, minor, patch } = *self;
143        write!(f, "{major}")?;
144        if let Some(minor) = minor {
145            write!(f, ".{minor}")?;
146        }
147        if let Some(patch) = patch {
148            if minor.is_none() {
149                f.write_str(".*")?;
150            }
151            write!(f, ".{patch}")?;
152        }
153        Ok(())
154    }
155}
156
157impl fmt::Debug for SemverVersion {
158    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
159        f.debug_struct("SemverVersion")
160            .field("span", &self.span)
161            .field("version", &format_args!("{self}"))
162            .finish()
163    }
164}
165
166impl From<semver::Version> for SemverVersion {
167    #[inline]
168    fn from(version: semver::Version) -> Self {
169        Self {
170            span: Span::DUMMY,
171            major: version.major.into(),
172            minor: Some(version.minor.into()),
173            patch: Some(version.patch.into()),
174        }
175    }
176}
177
178impl From<SemverVersion> for semver::Version {
179    #[inline]
180    fn from(version: SemverVersion) -> Self {
181        Self::new(
182            version.major.into(),
183            version.minor.map(Into::into).unwrap_or(0),
184            version.patch.map(Into::into).unwrap_or(0),
185        )
186    }
187}
188
189impl SemverVersion {
190    /// Creates a new [::semver] version from this version.
191    #[inline]
192    pub fn into_semver(self) -> semver::Version {
193        self.into()
194    }
195}
196
197/// A SemVer version requirement. This is a list of components, and is never empty.
198#[derive(Debug)]
199pub struct SemverReq<'ast> {
200    /// The components of this requirement.
201    ///
202    /// Or-ed list of and-ed components, meaning that `matches` is evaluated as
203    /// `any([all(c) for c in dis])`.
204    /// E.g.: `^0 <=1 || 0.5.0 - 0.6.0 ... || ...` -> `[[^0, <=1], [0.5.0 - 0.6.0, ...], ...]`
205    pub dis: BoxSlice<'ast, SemverReqCon<'ast>>,
206}
207
208impl fmt::Display for SemverReq<'_> {
209    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
210        for (i, con) in self.dis.iter().enumerate() {
211            if i > 0 {
212                f.write_str(" || ")?;
213            }
214            write!(f, "{con}")?;
215        }
216        Ok(())
217    }
218}
219
220impl SemverReq<'_> {
221    /// Returns `true` if the given version satisfies this requirement.
222    pub fn matches(&self, version: &SemverVersion) -> bool {
223        self.dis.iter().any(|c| c.matches(version))
224    }
225
226    /// Converts this requirement to a [::semver] version requirement.
227    pub fn to_semver(&self) -> SemverVersionReqCompat {
228        SemverVersionReqCompat { reqs: self.dis.iter().map(SemverReqCon::to_semver).collect() }
229    }
230}
231
232/// A list of or-ed [`semver::VersionReq`].
233///
234/// Obtained with [`SemverReq::to_semver`].
235pub struct SemverVersionReqCompat {
236    /// The list of requirements.
237    pub reqs: Vec<semver::VersionReq>,
238}
239
240impl SemverVersionReqCompat {
241    /// Returns `true` if the given version satisfies this requirement.
242    pub fn matches(&self, version: &semver::Version) -> bool {
243        self.reqs.iter().any(|r| r.matches(version))
244    }
245}
246
247/// A list of conjoint SemVer version requirement components.
248#[derive(Debug)]
249pub struct SemverReqCon<'ast> {
250    pub span: Span,
251    /// The list of components. See [`SemverReq::dis`] for more details.
252    pub components: BoxSlice<'ast, SemverReqComponent>,
253}
254
255impl fmt::Display for SemverReqCon<'_> {
256    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
257        for (j, component) in self.components.iter().enumerate() {
258            if j > 0 {
259                f.write_str(" ")?;
260            }
261            write!(f, "{component}")?;
262        }
263        Ok(())
264    }
265}
266
267impl SemverReqCon<'_> {
268    /// Converts this requirement to a [::semver] version requirement.
269    pub fn to_semver(&self) -> semver::VersionReq {
270        semver::VersionReq {
271            comparators: self.components.iter().flat_map(SemverReqComponent::to_semver).collect(),
272        }
273    }
274
275    /// Returns `true` if the given version satisfies this requirement.
276    pub fn matches(&self, version: &SemverVersion) -> bool {
277        self.components.iter().all(|c| c.matches(version))
278    }
279}
280
281/// A single SemVer version requirement component.
282#[derive(Clone, Debug)]
283pub struct SemverReqComponent {
284    pub span: Span,
285    pub kind: SemverReqComponentKind,
286}
287
288impl fmt::Display for SemverReqComponent {
289    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
290        self.kind.fmt(f)
291    }
292}
293
294impl SemverReqComponent {
295    /// Converts this requirement component to a [::semver] comparator.
296    pub fn to_semver(&self) -> SmallVec<[semver::Comparator; 2]> {
297        self.kind.to_semver()
298    }
299
300    /// Returns `true` if the given version satisfies this requirement component.
301    pub fn matches(&self, version: &SemverVersion) -> bool {
302        self.kind.matches(version)
303    }
304}
305
306/// A SemVer version requirement component.
307#[derive(Clone, Debug)]
308pub enum SemverReqComponentKind {
309    /// `v`, `=v`
310    Op(Option<Op>, SemverVersion),
311    /// `l - r`
312    Range(SemverVersion, SemverVersion),
313}
314
315impl fmt::Display for SemverReqComponentKind {
316    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
317        match self {
318            Self::Op(op, version) => {
319                if let Some(op) = op {
320                    let op = match op {
321                        Op::Exact => "=",
322                        Op::Greater => ">",
323                        Op::GreaterEq => ">=",
324                        Op::Less => "<",
325                        Op::LessEq => "<=",
326                        Op::Tilde => "~",
327                        Op::Caret => "^",
328                        Op::Wildcard => "*",
329                        _ => "",
330                    };
331                    f.write_str(op)?;
332                }
333                write!(f, "{version}")
334            }
335            Self::Range(left, right) => write!(f, "{left} - {right}"),
336        }
337    }
338}
339
340impl SemverReqComponentKind {
341    /// Converts this requirement component to a [::semver] comparator.
342    pub fn to_semver(&self) -> SmallVec<[semver::Comparator; 2]> {
343        let cvt_op = |op: Option<Op>, version: &SemverVersion| semver::Comparator {
344            op: op.unwrap_or(Op::Exact),
345            major: version.major.into(),
346            minor: version.minor.map(Into::into),
347            patch: version.patch.map(Into::into),
348            pre: Default::default(),
349        };
350        match self {
351            Self::Op(op, version) => smallvec![cvt_op(*op, version)],
352            Self::Range(start, end) => smallvec![
353                cvt_op(Some(semver::Op::GreaterEq), start),
354                cvt_op(Some(semver::Op::LessEq), end)
355            ],
356        }
357    }
358
359    /// Returns `true` if the given version satisfies this requirement component.
360    pub fn matches(&self, version: &SemverVersion) -> bool {
361        match self {
362            Self::Op(op, other) => matches_op(op.unwrap_or(Op::Exact), version, other),
363            Self::Range(start, end) => {
364                matches_op(Op::GreaterEq, version, start) && matches_op(Op::LessEq, version, end)
365            }
366        }
367    }
368}
369
370fn matches_op(op: Op, a: &SemverVersion, b: &SemverVersion) -> bool {
371    match op {
372        Op::Exact => a == b,
373        Op::Greater => a > b,
374        Op::GreaterEq => a >= b,
375        Op::Less => a < b,
376        Op::LessEq => a <= b,
377        Op::Tilde => matches_tilde(a, b),
378        Op::Caret => matches_caret(a, b),
379        Op::Wildcard => true,
380        _ => false,
381    }
382}
383
384fn matches_tilde(a: &SemverVersion, b: &SemverVersion) -> bool {
385    // https://github.com/argotorg/solidity/blob/e81f2bdbd66e9c8780f74b8a8d67b4dc2c87945e/liblangutil/SemVerHandler.cpp#L80
386    if !matches_op(Op::GreaterEq, a, b) {
387        return false;
388    }
389
390    let mut a = a.clone();
391    a.patch = None;
392    matches_op(Op::LessEq, &a, b)
393}
394
395fn matches_caret(a: &SemverVersion, b: &SemverVersion) -> bool {
396    // https://github.com/argotorg/solidity/blob/e81f2bdbd66e9c8780f74b8a8d67b4dc2c87945e/liblangutil/SemVerHandler.cpp#L95
397    if !matches_op(Op::GreaterEq, a, b) {
398        return false;
399    }
400
401    let mut a = a.clone();
402    if a.major > SemverVersionNumber::Number(0) {
403        a.minor = None;
404    }
405    a.patch = None;
406    matches_op(Op::LessEq, &a, b)
407}
408
409// Tests in `crates/parse/src/parser/item.rs`