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