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