Skip to main content

kcl_lib/
lib.rs

1//! Rust support for KCL (aka the KittyCAD Language).
2//!
3//! KCL is written in Rust. This crate contains the compiler tooling (e.g. parser, lexer, code generation),
4//! the standard library implementation, generator for the docs, and more.
5#![recursion_limit = "1024"]
6#![allow(clippy::boxed_local)]
7
8#[allow(unused_macros)]
9macro_rules! println {
10    ($($rest:tt)*) => {
11        #[cfg(all(feature = "disable-println", not(test)))]
12        {
13            let _ = format!($($rest)*);
14        }
15        #[cfg(any(not(feature = "disable-println"), test))]
16        std::println!($($rest)*)
17    }
18}
19
20#[allow(unused_macros)]
21macro_rules! eprintln {
22    ($($rest:tt)*) => {
23        #[cfg(all(feature = "disable-println", not(test)))]
24        {
25            let _ = format!($($rest)*);
26        }
27        #[cfg(any(not(feature = "disable-println"), test))]
28        std::eprintln!($($rest)*)
29    }
30}
31
32#[allow(unused_macros)]
33macro_rules! print {
34    ($($rest:tt)*) => {
35        #[cfg(all(feature = "disable-println", not(test)))]
36        {
37            let _ = format!($($rest)*);
38        }
39        #[cfg(any(not(feature = "disable-println"), test))]
40        std::print!($($rest)*)
41    }
42}
43
44#[allow(unused_macros)]
45macro_rules! eprint {
46    ($($rest:tt)*) => {
47        #[cfg(all(feature = "disable-println", not(test)))]
48        {
49            let _ = format!($($rest)*);
50        }
51        #[cfg(any(not(feature = "disable-println"), test))]
52        std::eprint!($($rest)*)
53    }
54}
55#[cfg(feature = "dhat-heap")]
56#[global_allocator]
57static ALLOC: dhat::Alloc = dhat::Alloc;
58
59pub mod collections;
60mod docs;
61mod engine;
62mod errors;
63mod execution;
64mod fmt;
65mod frontend;
66mod fs;
67pub(crate) mod id;
68mod import_format;
69pub mod lint;
70mod log;
71mod lsp_types;
72mod modules;
73pub mod named_views;
74mod parsing;
75mod project;
76mod runtime_flags;
77mod settings;
78#[cfg(test)]
79mod simulation_tests;
80pub mod std;
81#[cfg(not(target_arch = "wasm32"))]
82pub mod test_server;
83mod thread;
84#[doc(hidden)]
85pub mod tooling;
86pub mod unit_conversion;
87mod unparser;
88mod util;
89#[cfg(test)]
90mod variant_name;
91pub mod walk;
92#[cfg(target_arch = "wasm32")]
93mod wasm;
94
95/// Internal compiler APIs consumed by the standalone language-server crate.
96#[doc(hidden)]
97pub mod lsp_support {
98    pub mod docs {
99        pub mod kcl_doc {
100            pub use crate::docs::kcl_doc::ArgData;
101            pub use crate::docs::kcl_doc::DocData;
102            pub use crate::docs::kcl_doc::ModData;
103            pub use crate::docs::kcl_doc::walk_stdlib;
104        }
105    }
106
107    pub mod engine {
108        #[cfg(not(target_arch = "wasm32"))]
109        pub use crate::engine::new_zoo_client;
110    }
111
112    pub mod errors {
113        pub use crate::errors::Severity;
114        pub use crate::errors::Suggestion;
115    }
116
117    pub mod execution {
118        pub use crate::execution::ExecutorContext;
119        pub use crate::execution::MockConfig;
120
121        pub mod cache {
122            pub use crate::execution::cache::read_old_memory_var;
123        }
124
125        pub mod typed_path {
126            pub use crate::execution::typed_path::TypedPath;
127        }
128    }
129
130    pub mod fs {
131        pub use crate::fs::FileManager;
132        pub use crate::fs::FileSystemHandle;
133        pub use crate::fs::new_file_system_handle;
134
135        #[cfg(all(target_arch = "wasm32", not(test)))]
136        pub mod wasm {
137            pub use crate::fs::wasm::FileSystemManager;
138        }
139    }
140
141    pub mod parsing {
142        pub use crate::parsing::PIPE_OPERATOR;
143        pub use crate::parsing::parse_str;
144        pub use crate::parsing::parse_tokens;
145
146        pub mod ast {
147            pub mod types {
148                pub use crate::parsing::ast::types::*;
149            }
150        }
151
152        pub mod token {
153            pub use crate::parsing::token::RESERVED_WORDS;
154            pub use crate::parsing::token::TokenStream;
155            pub use crate::parsing::token::lex;
156
157            pub mod adapter {
158                pub use crate::parsing::token::adapter::lex_with_diagnostics;
159            }
160        }
161    }
162}
163
164pub use engine::AsyncTasks;
165pub use engine::EngineBatchContext;
166pub use engine::EngineStats;
167pub use errors::BacktraceItem;
168pub use errors::BacktraceItemKind;
169pub use errors::CompilationIssue;
170pub use errors::CompilationIssueReport;
171pub use errors::ConnectionError;
172pub use errors::ExecError;
173pub use errors::IsRetryable;
174pub use errors::KclError;
175pub use errors::KclErrorWithOutputs;
176pub use errors::Report;
177pub use errors::ReportWithOutputs;
178pub use errors::render_compilation_issue_miette;
179pub use execution::ConstraintKind;
180pub use execution::EdgeRefactorMeta;
181pub use execution::EnvironmentRef;
182pub use execution::ExecOutcome;
183pub use execution::ExecState;
184pub use execution::ExecutionCallbacks;
185pub use execution::ExecutorContext;
186pub use execution::ExecutorSettings;
187pub use execution::KclValueView;
188pub use execution::KclVersion;
189pub use execution::LegacyAngleRefactorMeta;
190pub use execution::MetaSettings;
191pub use execution::MockConfig;
192pub use execution::OperationCallbackArgs;
193pub use execution::Point2d;
194pub use execution::RefactorMetadata;
195pub use execution::SegmentDragAnchor;
196pub use execution::SketchConstraintReport;
197pub use execution::SketchConstraintStatus;
198pub use execution::bust_cache;
199pub use execution::clear_mem_cache;
200pub use execution::kcl_value::is_kcl_version_available;
201pub use execution::typed_path::TypedPath;
202pub use fs::FileSystem;
203pub use fs::FileSystemHandle;
204pub use fs::in_memory::InMemoryFiles;
205pub use fs::new_file_system_handle;
206pub use kcl_error;
207pub use kcl_error::SourceRange;
208pub use lsp_types::IntoDiagnostic;
209pub use lsp_types::LspSuggestion;
210pub use lsp_types::ToLspRange;
211pub use modules::ModuleId;
212pub use parsing::ast::types::FormatOptions;
213pub use parsing::ast::types::NodePath;
214pub use parsing::ast::types::NodePathExt;
215pub use parsing::ast::types::Program as AstProgram;
216pub use parsing::ast::types::Step as NodePathStep;
217pub use project::ProjectManager;
218pub use runtime_flags::KclRuntimeFlags;
219pub use runtime_flags::RuntimeFlag;
220pub use runtime_flags::kcl_runtime_flags;
221pub use runtime_flags::set_kcl_runtime_flags;
222pub use settings::types::Configuration;
223pub use settings::types::project::ProjectConfiguration;
224#[cfg(not(target_arch = "wasm32"))]
225pub use unparser::recast_dir;
226#[cfg(not(target_arch = "wasm32"))]
227pub use unparser::walk_dir;
228
229pub mod engine_connection {
230    pub use crate::engine::engine_manager::EngineManager;
231    pub use crate::engine::engine_manager::EngineTransport;
232    pub use crate::engine::engine_manager::ResponseInformation;
233    pub use crate::engine::engine_manager::SocketHealth;
234    pub use crate::engine::engine_manager::TransportCloseError;
235}
236
237// Rather than make executor public and make lots of it pub(crate), just re-export into a new module.
238// Ideally we wouldn't export these things at all, they should only be used for testing.
239pub mod exec {
240    pub use kcl_api::NumericType;
241    pub use kcl_api::UnitAngle;
242    pub use kcl_api::UnitLength;
243    pub use kcl_api::UnitType;
244
245    pub use crate::execution::ArtifactCommand;
246    pub use crate::execution::DefaultPlanes;
247    pub use crate::execution::IdGenerator;
248    pub use crate::execution::KclObjectKind;
249    pub use crate::execution::KclValue;
250    pub use crate::execution::KclValueView;
251    pub use crate::execution::Operation;
252    pub use crate::execution::PlaneKind;
253    pub use crate::execution::Sketch;
254    pub use crate::execution::annotations::WarningLevel;
255    pub use crate::util::RetryConfig;
256    pub use crate::util::execute_with_retries;
257}
258
259#[cfg(target_arch = "wasm32")]
260pub mod wasm_engine {
261    pub use crate::engine::conn_wasm::EngineCommandManager;
262    pub use crate::engine::conn_wasm::EngineConnection;
263    pub use crate::engine::conn_wasm::ResponseContext;
264    pub use crate::fs::wasm::FileManager;
265    pub use crate::fs::wasm::FileSystemManager;
266}
267
268pub mod std_utils {
269    pub use crate::std::utils::TangentialArcInfoInput;
270    pub use crate::std::utils::get_tangential_arc_to_info;
271    pub use crate::std::utils::is_points_ccw_wasm;
272    pub use crate::std::utils::untyped_point_to_unit;
273}
274
275pub mod pretty {
276    pub use crate::fmt::format_number_literal;
277    pub use crate::fmt::format_number_value;
278    pub use crate::fmt::human_display_number;
279}
280
281pub mod front {
282    pub use crate::frontend::EditConstraintOptions;
283    pub use crate::frontend::EditDistanceConstraintLabelPositionOptions;
284    pub use crate::frontend::EditSegmentsOptions;
285    pub use crate::frontend::FrontendState;
286    pub use crate::frontend::MAX_SKETCH_CHECKPOINTS;
287    pub use crate::frontend::SetProgramOutcome;
288    pub use crate::frontend::api::EditSketchOutcome;
289    pub use crate::frontend::api::Error;
290    pub use crate::frontend::api::File;
291    pub use crate::frontend::api::FileId;
292    pub use crate::frontend::api::LifecycleApi;
293    pub use crate::frontend::api::NewSketchOutcome;
294    pub use crate::frontend::api::NumberExt;
295    pub use crate::frontend::api::ObjectKindExt;
296    pub use crate::frontend::api::ProjectId;
297    pub use crate::frontend::api::RestoreSketchCheckpointOutcome;
298    pub use crate::frontend::api::Result;
299    pub use crate::frontend::api::SceneGraph;
300    pub use crate::frontend::api::SceneGraphDelta;
301    pub use crate::frontend::api::Settings;
302    pub use crate::frontend::api::SketchCheckpointId;
303    pub use crate::frontend::api::SketchMutationOutcome;
304    pub use crate::frontend::api::SourceDelta;
305    pub use crate::frontend::api::Version;
306    pub(crate) use crate::frontend::modify::find_defined_names;
307    pub(crate) use crate::frontend::modify::next_free_name_using_max;
308    pub use crate::frontend::sketch::ArcExt;
309    pub use crate::frontend::sketch::CircleExt;
310    pub use crate::frontend::sketch::CoincidentExt;
311    pub use crate::frontend::sketch::ConstraintLabelPositionEdit;
312    pub use crate::frontend::sketch::ControlPointSplineExt;
313    pub use crate::frontend::sketch::DistanceExt;
314    pub use crate::frontend::sketch::ExecResult;
315    pub use crate::frontend::sketch::ExistingSegmentCtor;
316    pub use crate::frontend::sketch::LineExt;
317    pub use crate::frontend::sketch::NewSegmentInfo;
318    pub use crate::frontend::sketch::PointExt;
319    pub use crate::frontend::sketch::SegmentExt;
320    pub use crate::frontend::sketch::SketchApi;
321    pub use crate::frontend::sketch::StartOrEnd;
322    pub use crate::frontend::trim::ArcPoint;
323    pub use crate::frontend::trim::AttachToEndpoint;
324    pub use crate::frontend::trim::CoincidentData;
325    pub use crate::frontend::trim::ConstraintToMigrate;
326    pub use crate::frontend::trim::Coords2d;
327    pub use crate::frontend::trim::EndpointChanged;
328    pub use crate::frontend::trim::LineEndpoint;
329    pub use crate::frontend::trim::TrimDirection;
330    pub use crate::frontend::trim::TrimItem;
331    pub use crate::frontend::trim::TrimOperation;
332    pub use crate::frontend::trim::TrimTermination;
333    pub use crate::frontend::trim::TrimTerminations;
334    pub use crate::frontend::trim::execute_trim_loop_with_context;
335    pub use crate::frontend::trim::get_next_trim_spawn;
336    pub use crate::frontend::trim::get_position_coords_for_line;
337    pub use crate::frontend::trim::get_position_coords_from_arc;
338    pub use crate::frontend::trim::is_point_on_line_segment;
339    pub use crate::frontend::trim::line_segment_intersection;
340    pub use crate::frontend::trim::perpendicular_distance_to_segment;
341    pub use crate::frontend::trim::project_point_onto_arc;
342    pub use crate::frontend::trim::project_point_onto_segment;
343}
344
345use serde::Deserialize;
346use serde::Serialize;
347
348use crate::exec::WarningLevel;
349#[allow(unused_imports)]
350use crate::log::log;
351#[allow(unused_imports)]
352use crate::log::logln;
353
354lazy_static::lazy_static! {
355
356    pub static ref IMPORT_FILE_EXTENSIONS: Vec<String> = {
357        import_format::IMPORT_FILE_EXTENSION_FORMATS
358            .iter()
359            .map(|(extension, _)| (*extension).to_owned())
360            .collect()
361    };
362
363    pub static ref RELEVANT_FILE_EXTENSIONS: Vec<String> = {
364        let mut relevant_extensions = IMPORT_FILE_EXTENSIONS.clone();
365        relevant_extensions.push("kcl".to_string());
366        relevant_extensions.push("md".to_string());
367        relevant_extensions
368    };
369}
370
371#[derive(Clone, Debug, PartialEq, Serialize)]
372pub struct Program {
373    pub kcl_version: KclVersion,
374    #[serde(flatten)]
375    pub ast: parsing::ast::types::Node<parsing::ast::types::Program>,
376    // The ui doesn't need to know about this.
377    // It's purely used for saving the contents of the original file, so we can use it for errors.
378    // Because in the case of the root file, we don't want to read the file from disk again.
379    #[serde(skip)]
380    pub original_file_contents: String,
381}
382
383impl<'de> Deserialize<'de> for Program {
384    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
385    where
386        D: serde::Deserializer<'de>,
387    {
388        #[derive(Deserialize)]
389        struct ProgramHelper {
390            #[serde(flatten)]
391            ast: parsing::ast::types::Node<parsing::ast::types::Program>,
392        }
393
394        let program = ProgramHelper::deserialize(deserializer)?;
395        // TypeScript ASTs can omit this field or set it incorrectly. Use our
396        // version resolution so annotations and the default are interpreted
397        // consistently and correctly.
398        let kcl_version = execution::computed_kcl_version(&program.ast);
399
400        Ok(Self {
401            kcl_version,
402            ast: program.ast,
403            original_file_contents: String::new(),
404        })
405    }
406}
407
408impl Program {
409    pub fn parse(input: &str) -> Result<(Option<Program>, Vec<CompilationIssue>), KclError> {
410        let module_id = ModuleId::default();
411        let (program, errs) = parsing::parse_str(input, module_id).0?;
412
413        Ok((
414            program.map(|program| Program {
415                kcl_version: program.kcl_version,
416                ast: program.ast,
417                original_file_contents: input.to_string(),
418            }),
419            errs,
420        ))
421    }
422
423    pub fn parse_no_errs(input: &str) -> Result<Program, KclError> {
424        let module_id = ModuleId::default();
425        let program = parsing::parse_str(input, module_id).parse_errs_as_err()?;
426
427        Ok(Program {
428            kcl_version: program.kcl_version,
429            ast: program.ast,
430            original_file_contents: input.to_string(),
431        })
432    }
433
434    pub fn compute_digest(&mut self) -> parsing::ast::digest::Digest {
435        self.ast.compute_digest()
436    }
437
438    /// Get the meta settings for the kcl file from the annotations.
439    pub fn meta_settings(&self) -> Result<Option<crate::MetaSettings>, KclError> {
440        self.ast.meta_settings()
441    }
442
443    /// Resolve the entry point's language version before connecting or executing.
444    /// Missing settings use the legacy version; invalid settings are errors.
445    pub fn language_version(&self) -> Result<KclVersion, KclError> {
446        Ok(self.meta_settings()?.unwrap_or_default().kcl_version)
447    }
448
449    /// Change the meta settings for the kcl file.
450    pub fn change_default_units(
451        &self,
452        length_units: Option<kittycad_modeling_cmds::units::UnitLength>,
453    ) -> Result<Self, KclError> {
454        Ok(Self {
455            kcl_version: self.kcl_version,
456            ast: self.ast.change_default_units(length_units)?,
457            original_file_contents: self.original_file_contents.clone(),
458        })
459    }
460
461    pub fn change_kcl_version(&self, kcl_version: Option<KclVersion>) -> Result<Self, KclError> {
462        Ok(Self {
463            kcl_version: kcl_version.unwrap_or_default(),
464            ast: self.ast.change_kcl_version(kcl_version)?,
465            original_file_contents: self.original_file_contents.clone(),
466        })
467    }
468
469    pub fn change_experimental_features(&self, warning_level: Option<WarningLevel>) -> Result<Self, KclError> {
470        Ok(Self {
471            kcl_version: self.kcl_version,
472            ast: self.ast.change_experimental_features(warning_level)?,
473            original_file_contents: self.original_file_contents.clone(),
474        })
475    }
476
477    pub fn is_empty_or_only_settings(&self) -> bool {
478        self.ast.is_empty_or_only_settings()
479    }
480
481    pub fn lint_all(&self) -> Result<Vec<lint::Discovered>, anyhow::Error> {
482        self.ast.lint_all()
483    }
484
485    pub fn lint<'a>(&'a self, rule: impl lint::Rule<'a>) -> Result<Vec<lint::Discovered>, anyhow::Error> {
486        self.ast.lint(rule)
487    }
488
489    pub fn node_path_from_range(&self, cached_body_items: usize, range: SourceRange) -> Option<NodePath> {
490        let module_infos = indexmap::IndexMap::new();
491        let programs = crate::execution::ProgramLookup::new(self.ast.clone(), module_infos);
492        NodePath::from_range(&programs, cached_body_items, range)
493    }
494
495    /// Fill node paths and consume the input so that the program without paths
496    /// isn't accidentally used. Filling node paths happens automatically during
497    /// parsing. Calling this is only needed after the caller invalidates the
498    /// node paths such as by mutating an AST or by making a round-trip through
499    /// serialization.
500    pub fn fill_node_paths(mut self) -> Program {
501        parsing::ast::types::fill_node_paths(&mut self.ast);
502        self
503    }
504
505    pub fn recast(&self) -> String {
506        // Use the default options until we integrate into the UI the ability to change them.
507        self.ast.recast_top(&Default::default(), 0)
508    }
509
510    pub fn recast_with_options(&self, options: &FormatOptions) -> String {
511        self.ast.recast_top(options, 0)
512    }
513
514    /// Create an empty program.
515    pub fn empty() -> Self {
516        Self {
517            kcl_version: KclVersion::default(),
518            ast: parsing::ast::types::Node::no_src(parsing::ast::types::Program::default()),
519            original_file_contents: String::new(),
520        }
521    }
522}
523
524#[inline]
525fn try_f64_to_usize(f: f64) -> Option<usize> {
526    let i = f as usize;
527    if i as f64 == f { Some(i) } else { None }
528}
529
530#[inline]
531fn try_f64_to_u32(f: f64) -> Option<u32> {
532    let i = f as u32;
533    if i as f64 == f { Some(i) } else { None }
534}
535
536#[inline]
537fn try_f64_to_u64(f: f64) -> Option<u64> {
538    let i = f as u64;
539    if i as f64 == f { Some(i) } else { None }
540}
541
542#[inline]
543fn try_f64_to_i64(f: f64) -> Option<i64> {
544    let i = f as i64;
545    if i as f64 == f { Some(i) } else { None }
546}
547
548/// Get the version of the KCL library.
549pub fn version() -> &'static str {
550    env!("CARGO_PKG_VERSION")
551}
552
553#[cfg(test)]
554mod test {
555    use super::*;
556
557    #[test]
558    fn program_deserializes_ast_without_kcl_version() {
559        for code in [
560            "",
561            "x = 1\n",
562            "@settings(defaultLengthUnit = mm)\nx = 1\n",
563            "@settings(kclVersion = 1.0)\nx = 1\n",
564            "@settings(kclVersion = 2.0)\nx = 1\n",
565            "@settings(kclVersion = 3.0)\nx = 1\n",
566            "@settings(kclVersion = \"3.0\")\nx = 1\n",
567            "@settings(kclVersion = \"3.0-preview\")\nx = 1\n",
568            "@settings(kclVersion = \"3-preview\")\nx = 1\n",
569            "@settings(kclVersion = 2.0)\n@settings(kclVersion = \"3.0-preview\")\nx = 1\n",
570            "@settings(kclVersion = \"3.0-preview\")\n@settings(kclVersion = 2.0)\nx = 1\n",
571            "@settings(kclVersion = 2.0)\n@settings(defaultLengthUnit = in)\nx = 1\n",
572            "@settings(kclVersion = 99.0)\nx = 1\n",
573        ] {
574            let parsed = Program::parse_no_errs(code).unwrap();
575            // TypeScript consumers construct ASTs without the wrapper's version field.
576            let json = serde_json::to_value(&parsed.ast).unwrap();
577            let deserialized: Program = serde_json::from_str(&json.to_string()).unwrap();
578
579            assert_eq!(deserialized.kcl_version, parsed.kcl_version, "{code}");
580            assert_eq!(deserialized.recast(), parsed.recast(), "{code}");
581        }
582    }
583
584    #[test]
585    fn program_deserialization_recomputes_kcl_version() {
586        for (code, supplied_version, expected_version) in [
587            ("x = 1\n", KclVersion::V2, KclVersion::V1),
588            (
589                "@settings(kclVersion = 2.0)\nx = 1\n",
590                KclVersion::V3Preview,
591                KclVersion::V2,
592            ),
593            (
594                "@settings(kclVersion = \"3.0-preview\")\nx = 1\n",
595                KclVersion::V2,
596                KclVersion::V3Preview,
597            ),
598            (
599                "@settings(kclVersion = 3.0)\nx = 1\n",
600                KclVersion::V3Preview,
601                KclVersion::V3,
602            ),
603            (
604                "@settings(kclVersion = \"3.0-preview\")\nx = 1\n",
605                KclVersion::V3,
606                KclVersion::V3Preview,
607            ),
608        ] {
609            let mut program = Program::parse_no_errs(code).unwrap();
610            program.kcl_version = supplied_version;
611
612            let json = serde_json::to_value(&program).unwrap();
613            let deserialized: Program = serde_json::from_str(&json.to_string()).unwrap();
614
615            assert_eq!(deserialized.kcl_version, expected_version, "{code}");
616            assert_eq!(deserialized.recast(), program.recast(), "{code}");
617            assert!(deserialized.original_file_contents.is_empty());
618        }
619    }
620
621    #[test]
622    fn entry_point_language_version() {
623        for (code, expected) in [
624            ("", KclVersion::V1),
625            ("@settings(defaultLengthUnit = mm)", KclVersion::V1),
626            ("@settings(kclVersion = 2.0)", KclVersion::V2),
627            ("@settings(kclVersion = 3.0)", KclVersion::V3),
628            ("@settings(kclVersion = \"3.0\")", KclVersion::V3),
629            ("@settings(kclVersion = \"3.0-preview\")", KclVersion::V3Preview),
630        ] {
631            assert_eq!(
632                Program::parse_no_errs(code).unwrap().language_version().unwrap(),
633                expected
634            );
635        }
636        let invalid = Program::parse_no_errs("@settings(kclVersion = 99.0)").unwrap();
637        invalid.language_version().unwrap_err();
638    }
639
640    #[test]
641    fn proprietary_file_extensions_use_real_suffixes() {
642        for extension in ["sat", "sab", "catpart", "prt", "ipt", "x_t", "x_b", "sldprt"] {
643            assert!(IMPORT_FILE_EXTENSIONS.iter().any(|candidate| candidate == extension));
644        }
645    }
646
647    #[test]
648    fn convert_int() {
649        assert_eq!(try_f64_to_usize(0.0), Some(0));
650        assert_eq!(try_f64_to_usize(42.0), Some(42));
651        assert_eq!(try_f64_to_usize(0.00000000001), None);
652        assert_eq!(try_f64_to_usize(-1.0), None);
653        assert_eq!(try_f64_to_usize(f64::NAN), None);
654        assert_eq!(try_f64_to_usize(f64::INFINITY), None);
655        assert_eq!(try_f64_to_usize((0.1 + 0.2) * 10.0), None);
656
657        assert_eq!(try_f64_to_u32(0.0), Some(0));
658        assert_eq!(try_f64_to_u32(42.0), Some(42));
659        assert_eq!(try_f64_to_u32(0.00000000001), None);
660        assert_eq!(try_f64_to_u32(-1.0), None);
661        assert_eq!(try_f64_to_u32(f64::NAN), None);
662        assert_eq!(try_f64_to_u32(f64::INFINITY), None);
663        assert_eq!(try_f64_to_u32((0.1 + 0.2) * 10.0), None);
664
665        assert_eq!(try_f64_to_u64(0.0), Some(0));
666        assert_eq!(try_f64_to_u64(42.0), Some(42));
667        assert_eq!(try_f64_to_u64(0.00000000001), None);
668        assert_eq!(try_f64_to_u64(-1.0), None);
669        assert_eq!(try_f64_to_u64(f64::NAN), None);
670        assert_eq!(try_f64_to_u64(f64::INFINITY), None);
671        assert_eq!(try_f64_to_u64((0.1 + 0.2) * 10.0), None);
672
673        assert_eq!(try_f64_to_i64(0.0), Some(0));
674        assert_eq!(try_f64_to_i64(42.0), Some(42));
675        assert_eq!(try_f64_to_i64(0.00000000001), None);
676        assert_eq!(try_f64_to_i64(-1.0), Some(-1));
677        assert_eq!(try_f64_to_i64(f64::NAN), None);
678        assert_eq!(try_f64_to_i64(f64::INFINITY), None);
679        assert_eq!(try_f64_to_i64((0.1 + 0.2) * 10.0), None);
680    }
681}