Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

fix: prevent conds lambda from caching the first length parameter #374

Merged
merged 4 commits into from Jul 13, 2023

Conversation

ribru17
Copy link
Contributor

@ribru17 ribru17 commented Jul 8, 2023

Problem:

I faced this issue when trying to write a simple rule for a block comment:

      npairs.add_rule(
        Rule('/**', '  */')
        :with_pair(cond.not_after_regex('.-%*/', -1))
        :set_end_pair_length(3)
      )

This rule kept failing and I thought I was going insane or just dumb but it turns out due to the odd nature of how variables are captured in lua. The first time that the not_after_regex function is run it instantiates the function to be returned with the cached value of length, and this is essentially always going to be the referenced value from within the returned function. I.e.:

cond.not_after_regex = function(regex, length)
    length = length or 1
    if not regex then
        return cond.none()
    end
    ---@param opts CondOpts
    return function(opts)
        log.debug('not_after_regex')
        if length < 0 then
            length = #opts.line
            -- LENGTH and #OPTS.LINE ARE NOT ALWAYS EQUAL HERE
        end
        local str = utils.text_sub_char(opts.line, opts.col, length)
        if str:match(regex) then
            return false
        end
    end
end

This is what caused my rule to fail. Instantiation a new local variable from within the returned lambda function fixes this issue in my testing, and I implemented it in the other regex related conditions as well.

cond.not_after_regex = function(regex, length)
    length = length or 1
    if not regex then
        return cond.none()
    end
    ---@param opts CondOpts
    return function(opts)
        log.debug('not_after_regex')
        local captured_length = length
        if captured_length < 0 then
            captured_length = #opts.line
            -- WORKS
        end
        local str = utils.text_sub_char(opts.line, opts.col, captured_length)
        if str:match(regex) then
            return false
        end
    end
end

Really hope this can get merged, thank you

…meter

Problem:

I faced this issue when trying to write a simple rule for a block comment:

```lua
      npairs.add_rule(
        Rule('/**', '  */')
        :with_pair(cond.not_after_regex('.-%*/', -1))
        :set_end_pair_length(3)
      )
```

This rule kept failing and I thought I was going insane or just dumb but it turns out due to the odd nature of how variables are captured in lua. The first time that the `not_after_regex` function is run it instantiates the function to be returned with the *cached* value of `length`, and this is essentially always going to be the referenced value from within the returned function. I.e.:

```lua
cond.not_after_regex = function(regex, length)
    length = length or 1
    if not regex then
        return cond.none()
    end
    ---@param opts CondOpts
    return function(opts)
        log.debug('not_after_regex')
        if length < 0 then
            length = #opts.line
            -- LENGTH and #OPTS.LINE ARE NOT ALWAYS EQUAL HERE
        end
        local str = utils.text_sub_char(opts.line, opts.col, length)
        if str:match(regex) then
            return false
        end
    end
end
```

This is what caused my rule to fail. Instantiation a new local variable from within the returned lambda function fixes this issue in my testing, and I implemented it in the other regex related conditions as well. Really hope this can get merged, thank you
@ribru17
Copy link
Contributor Author

ribru17 commented Jul 8, 2023

Here is a simpler example I made:

local function test(initial)
  initial = initial or 1
  return function(next)
    if initial < 0 then
      initial = next
    end
    print(initial)
  end
end

-- should always run `f` with initial = -1 (but does not)
local f = test(-1)

f(3) -- should print 3, prints 3

-- initial is now 3

f(7) -- should print 7, prints 3

@windwp
Copy link
Owner

windwp commented Jul 11, 2023

I think we should remove the length variable .
Can you remove it

@ribru17
Copy link
Contributor Author

ribru17 commented Jul 12, 2023

done 👍

@ribru17
Copy link
Contributor Author

ribru17 commented Jul 12, 2023

Would you mind telling me how to run the tests on my local machine? When I run make test many tests fail when they shouldn't, saying module 'nvim-surround' not found

@windwp
Copy link
Owner

windwp commented Jul 12, 2023

the rtp=. is nvim-autopairs folder

You need put nvim-autopairs and plenary and nvim treesitter in same folder.

Then you goto inside nvim-autopairs folder and run
make test

I will check that soon

@ribru17
Copy link
Contributor Author

ribru17 commented Jul 13, 2023

Realized one last bug in my fix. If this doesn't work I'll quit finally and let you take a look. still couldn't get tests running sadly, sorry

@windwp
Copy link
Owner

windwp commented Jul 13, 2023

Thanks 😍

@windwp windwp merged commit a16989a into windwp:master Jul 13, 2023
1 check passed
windwp pushed a commit that referenced this pull request Jul 14, 2023
@windwp
Copy link
Owner

windwp commented Jul 14, 2023

hi i just thinking about it then i add length param back.
Removing it would make many users to update their config for custom rules, which is something I would like to avoid.

so if you still need to add -1 to set length on regex

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.

None yet

2 participants