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

function: Deeply unmark function arguments before calling, reapply afterwards #72

Merged
merged 2 commits into from
Oct 13, 2020

Conversation

alisdair
Copy link
Contributor

@alisdair alisdair commented Oct 8, 2020

For functions which do not explicitly support marks, we now deeply unmark arguments before calling, and reapply those marks to the return value. This ensures that these functions do not panic with arguments which are collection values with marked descendants.

This does result in overly conservatively applying marks in some cases, but that can be fixed with later work to add explicit support for marks to those functions.

I've added tests for the join function, which I now think exhibits the correct behaviour.

Replaces #71

For functions which do not explicitly support marks, we now deeply
unmark arguments before calling, and reapply those marks to the return
value. This ensures that these functions do not panic with arguments
which are collection values with marked descendants.

This does result in overly conservatively applying marks in some cases,
but that can be fixed with later work to add explicit support for marks
to those functions.
@codecov
Copy link

codecov bot commented Oct 8, 2020

Codecov Report

Merging #72 into master will increase coverage by 0.20%.
The diff coverage is 12.50%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master      #72      +/-   ##
==========================================
+ Coverage   70.09%   70.30%   +0.20%     
==========================================
  Files          79       79              
  Lines        6534     6536       +2     
==========================================
+ Hits         4580     4595      +15     
+ Misses       1514     1498      -16     
- Partials      440      443       +3     
Impacted Files Coverage Δ
cty/function/function.go 37.71% <12.50%> (+1.11%) ⬆️
cty/convert/compare_types.go 97.53% <0.00%> (-2.47%) ⬇️
cty/function/stdlib/string.go 50.90% <0.00%> (+9.09%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 19f2b6b...48f7d40. Read the comment docs.

Copy link
Collaborator

@apparentlymart apparentlymart left a comment

Choose a reason for hiding this comment

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

Thanks for working on this, @alisdair!

I have a little ambivalence inline that I'm curious to hear your opinion on. Other than some indecision about some earlier code of mine, this seems great to me and I'd be happy to merge it either way.

resultMarks = append(resultMarks, marks)
args = newArgs
if !spec.AllowMarked {
unwrappedVal, marks := val.UnmarkDeep()
Copy link
Collaborator

Choose a reason for hiding this comment

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

I was going to suggest using val.ContainsMarked rather than val.Unmark here on the grounds that the old code here was trying to avoid reconstructing data structures unless it actually encountered something marked. However, having reviewed both functions it seems like ContainsMarked is currently potentially rather expensive too -- at the very least, it will allocate an array deep enough to represent the deepest cty.Path within the data structure -- so maybe it's not a big deal which to use.

Given that though, I wonder if the complexity of only conditionally copying the args array (as described in the comment below) is warranted anymore. Maybe we should now just args in all cases and just conditionally assign over zero or more of its elements, to make this overall easier to follow.

What do you think? I'm not greatly bothered by this, but I didn't really like the complexity of this conditional copying in the first place and had only really done it because val.IsMarked() is a relatively cheap operation that avoided doing a bunch of work in the common case.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My thinking here was that spec.AllowMarked is almost always going to be false. Of ~80 stdlib functions, only 9 currently support marked parameters. When UnmarkDeep is potentially so expensive, I felt the complexity trade-off was reasonable.

@apparentlymart
Copy link
Collaborator

Thanks for the thoughts on my comment above! Having had a little more time to reflect I agree with what you said and also see that this is an implementation detail we can always adjust over time if it ends up being a performance bottleneck, but that's relatively unlikely.

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.

2 participants