Skip to main content

perl_pragma/
version.rs

1use crate::PragmaState;
2
3/// Parsed Perl version from a lexical `use v...;` or `use 5.xxx;` pragma.
4#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
5pub struct PerlVersion {
6    /// Major Perl version component.
7    pub major: u32,
8    /// Minor Perl version component.
9    pub minor: u32,
10}
11
12impl PerlVersion {
13    /// Create a new Perl version value.
14    pub const fn new(major: u32, minor: u32) -> Self {
15        Self { major, minor }
16    }
17}
18
19/// Parse a Perl version string into a major/minor pair.
20///
21/// Handles lexical version pragmas such as:
22/// - `v5.36`
23/// - `v5.36.0`
24/// - `5.036`
25/// - `5.10`
26/// - developer releases like `5.012_001`
27pub fn parse_perl_version(module: &str) -> Option<PerlVersion> {
28    let s = module.strip_prefix('v').unwrap_or(module);
29    let mut parts = s.splitn(3, '.');
30
31    let major = parse_version_component(parts.next()?)?;
32    let minor = match parts.next() {
33        Some(part) => parse_version_component(part)?,
34        None => 0,
35    };
36
37    Some(PerlVersion::new(major, minor))
38}
39
40fn parse_version_component(component: &str) -> Option<u32> {
41    let component = component.split_once('_').map_or(component, |(head, _)| head);
42    component.parse().ok()
43}
44
45/// Whether `use VERSION` implies `strict` for this version.
46#[must_use]
47pub fn version_implies_strict(version: PerlVersion) -> bool {
48    version >= PerlVersion::new(5, 12)
49}
50
51/// Whether `use VERSION` implies `warnings` for this version.
52#[must_use]
53pub fn version_implies_warnings(version: PerlVersion) -> bool {
54    version >= PerlVersion::new(5, 35)
55}
56
57/// Returns the language features implicitly enabled by declaring `use VERSION`.
58///
59/// Mirrors the Perl `feature` pragma bundle semantics: each `use vX.Y`
60/// declaration implicitly enables the same features as `use feature ':X.Y'`.
61/// Features that were removed from a bundle (for example `switch` removed in
62/// v5.36 and `smartmatch` removed in v5.42) are not included for that
63/// version and above. Versions older than v5.10 load the `:default` bundle.
64///
65/// Reference: <https://perldoc.perl.org/feature#FEATURE-BUNDLES>
66#[must_use]
67pub fn features_enabled_by_version(version: PerlVersion) -> Vec<&'static str> {
68    let bundle = if version < PerlVersion::new(5, 10) {
69        DEFAULT_FEATURES
70    } else if version >= PerlVersion::new(5, 42) {
71        BUNDLE_5_42_FEATURES
72    } else if version >= PerlVersion::new(5, 40) {
73        BUNDLE_5_40_FEATURES
74    } else if version >= PerlVersion::new(5, 38) {
75        BUNDLE_5_38_FEATURES
76    } else if version >= PerlVersion::new(5, 36) {
77        BUNDLE_5_36_FEATURES
78    } else if version >= PerlVersion::new(5, 34) {
79        BUNDLE_5_34_FEATURES
80    } else if version >= PerlVersion::new(5, 28) {
81        BUNDLE_5_28_FEATURES
82    } else if version >= PerlVersion::new(5, 24) {
83        BUNDLE_5_24_FEATURES
84    } else if version >= PerlVersion::new(5, 16) {
85        BUNDLE_5_16_FEATURES
86    } else if version >= PerlVersion::new(5, 12) {
87        BUNDLE_5_12_FEATURES
88    } else {
89        BUNDLE_5_10_FEATURES
90    };
91
92    bundle.to_vec()
93}
94
95pub(crate) const DEFAULT_FEATURES: &[&str] = &[
96    "indirect",
97    "multidimensional",
98    "bareword_filehandles",
99    "apostrophe_as_package_separator",
100    "smartmatch",
101];
102
103const BUNDLE_5_10_FEATURES: &[&str] = &[
104    "apostrophe_as_package_separator",
105    "bareword_filehandles",
106    "indirect",
107    "multidimensional",
108    "say",
109    "smartmatch",
110    "state",
111    "switch",
112];
113
114const BUNDLE_5_12_FEATURES: &[&str] = &[
115    "apostrophe_as_package_separator",
116    "bareword_filehandles",
117    "indirect",
118    "multidimensional",
119    "say",
120    "smartmatch",
121    "state",
122    "switch",
123    "unicode_strings",
124];
125
126const BUNDLE_5_16_FEATURES: &[&str] = &[
127    "apostrophe_as_package_separator",
128    "bareword_filehandles",
129    "current_sub",
130    "evalbytes",
131    "fc",
132    "indirect",
133    "multidimensional",
134    "say",
135    "smartmatch",
136    "state",
137    "switch",
138    "unicode_eval",
139    "unicode_strings",
140];
141
142const BUNDLE_5_24_FEATURES: &[&str] = &[
143    "apostrophe_as_package_separator",
144    "bareword_filehandles",
145    "current_sub",
146    "evalbytes",
147    "fc",
148    "indirect",
149    "multidimensional",
150    "postderef_qq",
151    "say",
152    "smartmatch",
153    "state",
154    "switch",
155    "unicode_eval",
156    "unicode_strings",
157];
158
159const BUNDLE_5_28_FEATURES: &[&str] = &[
160    "apostrophe_as_package_separator",
161    "bareword_filehandles",
162    "bitwise",
163    "current_sub",
164    "evalbytes",
165    "fc",
166    "indirect",
167    "multidimensional",
168    "postderef_qq",
169    "say",
170    "smartmatch",
171    "state",
172    "switch",
173    "unicode_eval",
174    "unicode_strings",
175];
176
177const BUNDLE_5_34_FEATURES: &[&str] = BUNDLE_5_28_FEATURES;
178
179const BUNDLE_5_36_FEATURES: &[&str] = &[
180    "apostrophe_as_package_separator",
181    "bareword_filehandles",
182    "bitwise",
183    "current_sub",
184    "evalbytes",
185    "fc",
186    "isa",
187    "postderef_qq",
188    "say",
189    "signatures",
190    "smartmatch",
191    "state",
192    "unicode_eval",
193    "unicode_strings",
194];
195
196const BUNDLE_5_38_FEATURES: &[&str] = &[
197    "apostrophe_as_package_separator",
198    "bitwise",
199    "current_sub",
200    "evalbytes",
201    "fc",
202    "isa",
203    "module_true",
204    "postderef_qq",
205    "say",
206    "signatures",
207    "smartmatch",
208    "state",
209    "unicode_eval",
210    "unicode_strings",
211];
212
213const BUNDLE_5_40_FEATURES: &[&str] = &[
214    "apostrophe_as_package_separator",
215    "bitwise",
216    "current_sub",
217    "evalbytes",
218    "fc",
219    "isa",
220    "module_true",
221    "postderef_qq",
222    "say",
223    "signatures",
224    "smartmatch",
225    "state",
226    "try",
227    "unicode_eval",
228    "unicode_strings",
229];
230
231const BUNDLE_5_42_FEATURES: &[&str] = &[
232    "bitwise",
233    "current_sub",
234    "evalbytes",
235    "fc",
236    "isa",
237    "module_true",
238    "postderef_qq",
239    "say",
240    "signatures",
241    "state",
242    "try",
243    "unicode_eval",
244    "unicode_strings",
245];
246
247pub(crate) fn enable_effective_version_semantics(state: &mut PragmaState, version: PerlVersion) {
248    if version_implies_strict(version) {
249        state.strict_vars = true;
250        state.strict_subs = true;
251        state.strict_refs = true;
252    }
253    if version_implies_warnings(version) {
254        state.warnings = true;
255    }
256    // Populate the version-implied feature set.
257    // Replace (not merge) so the highest `use vX.Y` wins if multiple appear.
258    state.features = features_enabled_by_version(version);
259    state.unicode_strings = state.has_feature("unicode_strings");
260    state.signatures_strict = false;
261}