Skip to content

Commit

Permalink
fix: dont strip empty () on constructors (foundry-rs#6740)
Browse files Browse the repository at this point in the history
* fix: dont strip empty () on constructors

* else if
  • Loading branch information
mattsse authored and RPate97 committed Jan 12, 2024
1 parent 25392c0 commit bacf5b0
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 0 deletions.
35 changes: 35 additions & 0 deletions crates/fmt/src/formatter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,13 @@ struct Context {
if_stmt_single_line: Option<bool>,
}

impl Context {
/// Returns true if the current function context is the constructor
pub(crate) fn is_constructor_function(&self) -> bool {
self.function.as_ref().map_or(false, |f| matches!(f.ty, FunctionTy::Constructor))
}
}

/// A Solidity formatter
#[derive(Debug)]
pub struct Formatter<'a, W> {
Expand Down Expand Up @@ -3047,6 +3054,18 @@ impl<'a, W: Write> Visitor for Formatter<'a, W> {
self.visit_list("", args, None, Some(loc.end()), false)?
}
FunctionAttribute::BaseOrModifier(loc, base) => {
// here we need to find out if this attribute belongs to the constructor because the
// modifier need to include the trailing parenthesis
// This is very ambiguous because the modifier can either by an inherited contract
// or a modifier here: e.g.: This is valid constructor:
// `constructor() public Ownable() OnlyOwner {}`
let is_constructor = self.context.is_constructor_function();
// we can't make any decisions here regarding trailing `()` because we'd need to
// find out if the `base` is a solidity modifier or an
// interface/contract therefor we we its raw content.

// we can however check if the contract `is` the `base`, this however also does
// not cover all cases
let is_contract_base = self.context.contract.as_ref().map_or(false, |contract| {
contract.base.iter().any(|contract_base| {
contract_base
Expand All @@ -3060,6 +3079,20 @@ impl<'a, W: Write> Visitor for Formatter<'a, W> {

if is_contract_base {
base.visit(self)?;
} else if is_constructor {
// This is ambiguous because the modifier can either by an inherited
// contract modifiers with empty parenthesis are
// valid, but not required so we make the assumption
// here that modifiers are lowercase
let mut base_or_modifier =
self.visit_to_chunk(loc.start(), Some(loc.end()), base)?;
let is_lowercase =
base_or_modifier.content.chars().next().map_or(false, |c| c.is_lowercase());
if is_lowercase && base_or_modifier.content.ends_with("()") {
base_or_modifier.content.truncate(base_or_modifier.content.len() - 2);
}

self.write_chunk(&base_or_modifier)?;
} else {
let mut base_or_modifier =
self.visit_to_chunk(loc.start(), Some(loc.end()), base)?;
Expand Down Expand Up @@ -3110,6 +3143,8 @@ impl<'a, W: Write> Visitor for Formatter<'a, W> {
})?;

if base.args.is_none() || base.args.as_ref().unwrap().is_empty() {
// This is ambiguous because the modifier can either by an inherited contract or a
// modifier
if self.context.function.is_some() {
name.content.push_str("()");
}
Expand Down
13 changes: 13 additions & 0 deletions crates/fmt/testdata/ConstructorModifierStyle/fmt.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.5.2;

import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {ERC1155} from "solmate/tokens/ERC1155.sol";

import {IAchievements} from "./interfaces/IAchievements.sol";
import {SoulBound1155} from "./abstracts/SoulBound1155.sol";

contract Achievements is IAchievements, SoulBound1155, Ownable {
constructor(address owner) Ownable() ERC1155() {}
}
13 changes: 13 additions & 0 deletions crates/fmt/testdata/ConstructorModifierStyle/original.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.5.2;

import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {ERC1155} from "solmate/tokens/ERC1155.sol";

import {IAchievements} from "./interfaces/IAchievements.sol";
import {SoulBound1155} from "./abstracts/SoulBound1155.sol";

contract Achievements is IAchievements, SoulBound1155, Ownable {
constructor(address owner) Ownable() ERC1155() {}
}
1 change: 1 addition & 0 deletions crates/fmt/tests/formatter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ macro_rules! test_directories {

test_directories! {
ConstructorDefinition,
ConstructorModifierStyle,
ContractDefinition,
DocComments,
EnumDefinition,
Expand Down

0 comments on commit bacf5b0

Please sign in to comment.