Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@dherman
Copy link
Collaborator

@dherman dherman commented Sep 2, 2025

Example:

#[derive(Debug, Clone)]
pub struct Point {
    x: u32,
    y: u32,
}

#[neon::class]
impl Point {
    pub fn new(x: u32, y: u32) -> Self {
        Self { x, y }
    }

    pub fn x(&self) -> u32 {
        self.x
    }

    pub fn y(&self) -> u32 {
        self.y
    }

    pub fn distance(&self, other: &Self) -> f64 {
        let dx = (self.x as i32 - other.x as i32).pow(2);
        let dy = (self.y as i32 - other.y as i32).pow(2);
        ((dx + dy) as f64).sqrt()
    }
}

Tasks:

  • #[neon::export(class)] shortcut
  • auto-JS-ify method names
  • include other export(function) bells & whistles like JSON, async, task
  • support const declarations
  • can we support some amount of subclassing?
  • rustdocs
  • cleanup

@dherman dherman marked this pull request as ready for review September 25, 2025 02:35
@dherman dherman requested a review from kjvalencik September 25, 2025 02:36
@dherman dherman changed the title [WIP] Class macro Class macro Sep 25, 2025
@kjvalencik kjvalencik changed the base branch from kv/wrap to main September 26, 2025 19:06
- Class trait
- #[neon::class] macro
- Instance extractor
- supports ability to construct instances both from JS and Rust
- a couple of unit tests

remaining to do:
- how much subclassing can we support?
- #[neon::export(class)] shorthand
- cleanup
- allow #[neon(name = "foobar")] explicit renames in method attributes
- implementation of async and task methods
- doesn't yet work in conjunction with async fn
- takes &self
- doesn't implicitly clone
- added a couple of tests
- add tests for subclassing
- suppress unused_variables warning in test
- bugfix: async fn with json attribute now works
- add missing files that I meant to add in earlier commits!
- clarifications in export macro rustdocs
- suppress lint warnings for auto-generated hidden exports
@codecov
Copy link

codecov bot commented Oct 8, 2025

Codecov Report

❌ Patch coverage is 84.55115% with 148 lines in your changes missing coverage. Please review.
✅ Project coverage is 83.24%. Comparing base (5793b62) to head (0676c28).

Files with missing lines Patch % Lines
crates/neon-macros/src/class/mod.rs 88.88% 56 Missing and 5 partials ⚠️
crates/neon/src/object/wrap.rs 79.81% 22 Missing ⚠️
crates/neon-macros/src/class/meta.rs 71.18% 13 Missing and 4 partials ⚠️
crates/neon/src/types_impl/extract/error.rs 0.00% 14 Missing ⚠️
crates/neon-macros/src/export/class/meta.rs 56.66% 9 Missing and 4 partials ⚠️
crates/neon-macros/src/export/class.rs 78.57% 5 Missing and 1 partial ⚠️
crates/neon/src/types_impl/extract/mod.rs 57.14% 3 Missing and 3 partials ⚠️
crates/neon-macros/src/name.rs 96.26% 3 Missing and 1 partial ⚠️
crates/neon/src/macro_internal/mod.rs 55.55% 3 Missing and 1 partial ⚠️
crates/neon-macros/src/export/mod.rs 75.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1117      +/-   ##
==========================================
+ Coverage   82.85%   83.24%   +0.38%     
==========================================
  Files          74       81       +7     
  Lines        4701     5610     +909     
  Branches     4701     5610     +909     
==========================================
+ Hits         3895     4670     +775     
- Misses        713      830     +117     
- Partials       93      110      +17     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

- automatically wrap RefCell<T> instead of T
- no longer require Clone unless class includes async or task methods
value: String,
}

#[neon::export(class, name = "RenamedClass")]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

name is a bit ambiguous here. Are we renaming the class or are we renaming the export? Should we have two different name fields?

// #[neon::export(class, name = "RenamedClass")]
// impl CustomNamedClass {}
// equivalent to...
export const RenamedClass = class CustomNamedClass {}

// #[neon::export(class(name = "RenamedClass"))]
// impl CustomNamedClass {}
// equivalent to...
export const CustomNamedClass = class RenamedClass {}

// #[neon::export(class(name = "RenamedClass"), name = "RenamedClass")]
// impl CustomNamedClass {}
// equivalent to...
export class RenamedClass {}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly to avoid duplication in the common case of renaming both:

// #[neon::export(class, name = "RenamedClass")]
// impl CustomNamedClass {}
// equivalent to...
export const RenamedClass = class CustomNamedClass {}

// #[neon::export(class(name = "RenamedClass"))]
// impl CustomNamedClass {}
// equivalent to...
export class RenamedClass {}

// #[neon::export(class(name = "RenamedClass"), name = "CustomNamedClass")]
// impl CustomNamedClass {}
// equivalent to...
export const CustomNamedClass = class RenamedClass {}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After discussing, we came to this conclusion:

// CASE 1: renames both to RenamedClass, but we won't document this
#[neon::export(class, name = "RenamedClass")]
impl CustomNamedClass {}

// CASE 2: renames both to RenamedClass, we will document this
#[neon::export(class(name = "RenamedClass"))]
impl CustomNamedClass {}

// CASE 3: renames class to RenamedClass and export to ExportedRenamedClass, we will document this
#[neon::export(class(name = "RenamedClass"), name = "ExportedRenamedClass"]
impl CustomNamedClass {}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rationale:

  • Case 1 mirrors JS syntax and avoids accidentally leaking the internal Rust implementation details
  • Case 2 is nicely compositional
  • Case 3 is probably rare but again it's compositional

- Instance has been deleted
- updated docs
@dherman
Copy link
Collaborator Author

dherman commented Oct 13, 2025

Plan:

  • add back extractor for TryFromJs for explicit clones
  • UPDATE: we don't need an extractor! we can use this hack to use a "non-trivial bound" where for<'a> Self: Clone
  • don't need extractor for TryIntoJs
  • add TryFromJs for & and &mut as well, no extractor needed

- macro generates a `where Self: Clone` clause to conditionally require Clone only if a function takes the class type as a parameter
- use a compiler workaround hack to enable this functionality -- see rust-lang/rust#48214 (comment)
@dherman dherman requested a review from kjvalencik October 28, 2025 22:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants