Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Revert context changes on error #508

Merged
merged 13 commits into from
Oct 18, 2024

Conversation

hdwalters
Copy link
Contributor

Fixes bug #489, by using a procedural macro to generate context manager functions, wrapping syntax parser failure. Also pops scope from the scope stack on syntax parser failure.

@hdwalters hdwalters requested review from Mte90 and Ph0enixKM October 6, 2024 13:37
Cargo.toml Show resolved Hide resolved
meta/src/manager.rs Outdated Show resolved Hide resolved
src/utils/metadata/parser.rs Show resolved Hide resolved
@Ph0enixKM Ph0enixKM requested a review from KrosFire October 6, 2024 19:44
@Ph0enixKM Ph0enixKM linked an issue Oct 6, 2024 that may be closed by this pull request
@Ph0enixKM Ph0enixKM added bug Something isn't working compiler labels Oct 6, 2024
@Mte90 Mte90 removed their request for review October 7, 2024 08:22
Copy link
Member

@KrosFire KrosFire left a comment

Choose a reason for hiding this comment

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

Looks pretty good, but I believe it needs more documentation. It took me a while to understand this. But maybe I'm just stupid XD

src/utils/metadata/parser.rs Outdated Show resolved Hide resolved
meta/src/manager.rs Show resolved Hide resolved
meta/src/manager.rs Outdated Show resolved Hide resolved
meta/src/manager.rs Outdated Show resolved Hide resolved
meta/src/manager.rs Show resolved Hide resolved
meta/src/helper.rs Show resolved Hide resolved
Comment on lines +38 to +46
fn visit_field(&mut self, field: &'a Field) {
if field.attrs.iter().any(utils::is_context) {
if let Some(name) = &field.ident {
if let Some(segment) = utils::get_type(field) {
self.functions.push(Self::make_function(name, segment));
}
}
}
}
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
fn visit_field(&mut self, field: &'a Field) {
if field.attrs.iter().any(utils::is_context) {
if let Some(name) = &field.ident {
if let Some(segment) = utils::get_type(field) {
self.functions.push(Self::make_function(name, segment));
}
}
}
}
fn visit_field(&mut self, field: &'a Field) {
if !field.attrs.iter().any(utils::is_context) {
return;
}
if let (Some(name), Some(segment)) = (&field.ident, utils::get_type(field)) {
self.functions.push(Self::make_function(name, segment));
}
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not sure your suggested tuple destructuring makes things any clearer; better to keep unrelated functionality on separate lines.

Copy link
Member

Choose a reason for hiding this comment

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

@Ph0enixKM wdyt? Imo it looks clearer, but It's just a preference so vox populi, vox Dei

Copy link
Member

Choose a reason for hiding this comment

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

The modified version looks clearer because it favors the never-nesting approach. I like the change that @KrosFire made.

Early return is more verbose. If we had even more nested if..let then this change would highlight the issue even more. The code will keep the indentation level while the original nested code will be a bunch of if's nested inside.

Comment on lines 15 to 23
if let Meta::Path(path) = &attr.meta {
if let Some(segment) = path.segments.last() {
let ident = segment.ident.to_string();
if ident == "context" {
return true;
}
}
}
false
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
if let Meta::Path(path) = &attr.meta {
if let Some(segment) = path.segments.last() {
let ident = segment.ident.to_string();
if ident == "context" {
return true;
}
}
}
false
if let Meta::Path(path) = &attr.meta {
return path
.segments
.last()
.map_or(false, |segment| segment.ident == "context");
}
false

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not sure this makes things any clearer.

Copy link
Member

@Ph0enixKM Ph0enixKM Oct 15, 2024

Choose a reason for hiding this comment

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

I think that what @KrosFire suggested is slightly more readable because it eliminates the need for conversion .to_string()

Copy link
Contributor Author

@hdwalters hdwalters Oct 15, 2024

Choose a reason for hiding this comment

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

I've removed the unnecessary .to_string(), but that .map_or() construct seems unnecessarily verbose. I tend to write nested chains of if statements, because it puts each thing being tested on a single line, reducing the conceptual load.

meta/src/utils.rs Outdated Show resolved Hide resolved
@hdwalters hdwalters force-pushed the revert-context-changes-on-error branch from 814f88b to dbc4de3 Compare October 8, 2024 06:36
@hdwalters hdwalters requested a review from KrosFire October 8, 2024 07:51
Copy link
Member

@KrosFire KrosFire left a comment

Choose a reason for hiding this comment

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

LGTM

meta/src/lib.rs Outdated Show resolved Hide resolved
@hdwalters hdwalters force-pushed the revert-context-changes-on-error branch 2 times, most recently from 2b7665e to 1ef6f28 Compare October 9, 2024 22:14
@hdwalters hdwalters force-pushed the revert-context-changes-on-error branch from 1ef6f28 to 7d05976 Compare October 9, 2024 22:23
@@ -7,7 +7,7 @@ use std::{io::{BufWriter, Write}, process::{Command, Stdio}};
#[derive(Debug, Clone, Copy)]
#[allow(non_camel_case_types)]
pub enum BashFormatter {
/// https://github.com/mvdan/sh
/// <https://github.com/mvdan/sh>
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Change was suggested by RustRover. Not sure if it's sensible; perhaps someone who knows more about doc comments than I do could verify?

Copy link
Member

@Ph0enixKM Ph0enixKM Oct 15, 2024

Choose a reason for hiding this comment

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

As per rust book:

bare_urls detects links that are not linkified, e.g., in Markdown such as Go to https://example.com/. It suggests wrapping the link with angle brackets: Go to <https://example.com/>. to linkify it. This is the code behind the rustdoc::bare_urls lint.

To be honest I didn't know that before! It's always nice to learn something new

Copy link
Member

@Ph0enixKM Ph0enixKM left a comment

Choose a reason for hiding this comment

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

I'm thinking whether or not is the whole proc macro approach a bit of an overkill for such a simple use case. Is there something that macro_rules was inappropriate? The proc macro requires to create a whole new repository. Using macro_rules requires a lot less effort to read the macro, because it usually can be expressed in a single file.

I'm saying that because the implementation for the functions isn't that complex and it seems that we could use a simpler macro for this.

macro_rules! with_context {
	(...) => {
		let prev = self.#name.clone();
		self.#name = #name;
		let result = body(self);
		self.#name = prev;
		result
	}

	// ... Maybe some other implementations for `ref` and `fn`
}

And then the usage would be:

with_context!(..., |...| {

})

Anyways... what do you think @hdwalters @KrosFire?

@hdwalters
Copy link
Contributor Author

hdwalters commented Oct 15, 2024

I'm thinking whether or not is the whole proc macro approach a bit of an overkill for such a simple use case. Is there something that macro_rules was inappropriate? The proc macro requires to create a whole new repository. Using macro_rules requires a lot less effort to read the macro, because it usually can be expressed in a single file.
...
Anyways... what do you think @hdwalters @KrosFire?

I think the proc_macro_derive approach is more elegant, because you can turn functionality on and off with Rust annotations, which are (by nature) inline with what they're annotating. The problem with your macro_rules! suggestion is that the macro call site is physically separate from the code it references, and can therefore get out of step.

Also, as I understand it, we do not need to create a new Git repository, just a new Cargo.toml file in a subdirectory; running cargo build on the root directory appears to build the macro dependency automatically.

Also, depending on your IDE (RustRover does this) you can show the results of macro expansion by hovering over the relevant item in the source. This certainly helped me write this!

@hdwalters
Copy link
Contributor Author

hdwalters commented Oct 15, 2024

Also, as I understand it, we do not need to create a new Git repository, just a new Cargo.toml file in a subdirectory; running cargo build on the root directory appears to build the macro dependency automatically.

Touched one of the subdirectory source files and rebuilt. The dependency was automatically rebuilt:

hwalters@Ghostwheel ~/git/amber (revert-context-changes-on-error) 
$ touch meta/src/lib.rs 
hwalters@Ghostwheel ~/git/amber (revert-context-changes-on-error) 
$ cargo build
   Compiling amber-meta v0.1.0 (/home/hwalters/git/amber/meta)
   Compiling amber v0.3.5-alpha (/home/hwalters/git/amber)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 2.12s

So the CI server should continue to work; and I don't believe we're pushing the source to https://crates.io/ or anywhere similar, so there shouldn't be any problems there either.

@Ph0enixKM
Copy link
Member

The problem with your macro_rules! suggestion is that the macro call site is physically separate from the code it references, and can therefore get out of step.

@hdwalters could you elaborate on that? I still don't understand the point. You can always modify the variable with std::mem::swap. Oh maybe you meant that the fact that the macro implements a method - this will tell the user to use the method of the struct instead of looking for a macro that somehow is meant to be used for it? I agree then. I didn't see it this way. Good call.

@Ph0enixKM
Copy link
Member

We could migrate other macros to this proc macro to enhance the experience even more. But that's not the most important thing to be worried about right now.

Copy link
Member

@Ph0enixKM Ph0enixKM left a comment

Choose a reason for hiding this comment

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

LGTM

@hdwalters
Copy link
Contributor Author

hdwalters commented Oct 17, 2024

@hdwalters could you elaborate on that? I still don't understand the point. You can always modify the variable with std::mem::swap. Oh maybe you meant that the fact that the macro implements a method - this will tell the user to use the method of the struct instead of looking for a macro that somehow is meant to be used for it? I agree then. I didn't see it this way. Good call.

Actually I meant that it's better to configure macro behaviour with annotations, because they're right next to the annotated code, so it's more obvious what's available:

#[derive(ContextManager)]
struct Amplifier {
    #[context]
    power: bool,
    ...
}

But your point is valid too; the main thing about procedural macros is that they operate directly on and (in this case) add functions to the abstract syntax tree, exactly as if you had typed them yourself.

@hdwalters
Copy link
Contributor Author

hdwalters commented Oct 17, 2024

Declarative macros with macro_rules! are better for things like print!() and vec![], where they're not tied to a particular struct or field.

@hdwalters
Copy link
Contributor Author

Rust macros are so much cooler than C++ ones!

@b1ek b1ek merged commit 4fa2909 into amber-lang:master Oct 18, 2024
1 check passed
@hdwalters hdwalters deleted the revert-context-changes-on-error branch October 18, 2024 09:31
# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
bug Something isn't working compiler
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[BUG] Context values not reset on failure
4 participants