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

Case splitting and lambda introduction #391

Merged
merged 10 commits into from
Sep 16, 2020
Merged

Conversation

isovector
Copy link
Collaborator

@isovector isovector commented Sep 9, 2020

@TOTBWF and I had a mini hackathon last weekend and this is the results. It provides case splitting, homomorphic case splitting[^1], and lambda introduction. It does a reasonable job of coming up with good names, and implements a lot of general-purpose machinery for other plugins.

[^1] Case splitting to the same datacons --- useful for writing things like functor instances.

It looks like this:

Peek 2020-09-09 14-31

Fixes #323 --- although that usecase could be improved with a separate action that flattens foo = \x y z -> bar into foo x y z = bar.

We have plans to follow this up with more type-directed code generation; a great deal of it is already implemented, but not yet polished. Most of Ide.Plugin.Tactic.Tactics is towards that purpose.

@isovector
Copy link
Collaborator Author

I think this needs refinery added to the stack.yamls, but was hoping CI could tell me.

@expipiplus1
Copy link
Contributor

It would be nice if it were configurable to generate LambdaCase splits too.

For the example in OP:

fmapEither = \fab -> \case
  Left c -> Left _ 
  Right a -> Right _

Of course this feature request shouldn't interfere with this being merged!

Copy link
Collaborator

@pepeiborra pepeiborra left a comment

Choose a reason for hiding this comment

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

Super cool, thanks @isovector and @TOTBWF !!!!

I won't claim to understand all that is going on, but see my comments for minor nits on ghcide specifics.

Tests would be very welcome in future PRs to ensure that the plugin doesn't break accidentally.

src/Ide/LocalBindings.hs Outdated Show resolved Hide resolved
src/Ide/Plugin/Tactic/Machinery.hs Outdated Show resolved Hide resolved
src/Ide/Plugin/Tactic/Tactics.hs Outdated Show resolved Hide resolved
src/Ide/TreeTransform.hs Outdated Show resolved Hide resolved
src/Ide/Plugin/Tactic.hs Outdated Show resolved Hide resolved
@isovector
Copy link
Collaborator Author

@pepeiborra what sorts of tests would you be interested in? Things to show the tactics do what they say? Or tests more along the lines of the plugin integration at large?

-- **WARNING:** This doesn't yet work over TH splices or arrow syntax --- and
-- possibly other obscure pieces of the AST.
dataBindings :: Data a => S.Set Id -> a -> M.Map SrcSpan (S.Set Id)
dataBindings in_scope = foldMapOf biplate $ cool collect
Copy link
Collaborator

@wz1000 wz1000 Sep 10, 2020

Choose a reason for hiding this comment

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

This information is computed in .hie files. If you look at the refmap in the result of the GetHieFile rule, you will get a map Map Identifier [(Span, IdentifierDetails Type)]. IdentifierDetails contains a set of ContextInfo, from which you recover the scope for that particular identifier using getScopeFromContext.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'd love to get rid of this; but if I'm reading your comment correctly, that mapping is the wrong direction. We want to go from a span to every binding in scope inside of it.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes, you need to invert the map into something like a IntervalMap, which should be pretty straightforward to construct. What we have is essentially a Map Name [Scope], what we need is an IntervalMap SrcLoc Name, right?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Delightfully good idea. I'll implement it today. Thanks!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I gave this a go, and got as far as getting back a TypeIndex from hie. But I can't figure out how to reconstitute this into a proper GHC Type, which is necessary for how we've set up refinery. Holding off on moving forwards until someone can point me in the right direction on that one!

Copy link
Collaborator

Choose a reason for hiding this comment

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

See #396, that should make this work

@pepeiborra
Copy link
Collaborator

@pepeiborra what sorts of tests would you be interested in? Things to show the tactics do what they say? Or tests more along the lines of the plugin integration at large?

See the tests for the Eval plugin - golden tests to show that all the plugin salient features work as expected.

@isovector
Copy link
Collaborator Author

I'll tackle the tests and suggestions tomorrow :)

@isovector
Copy link
Collaborator Author

@expipiplus1 happy to implement that immediately after. Please file a bug if one doesn't already exist!

@expipiplus1
Copy link
Contributor

@isovector done, thanks! #394

@isovector
Copy link
Collaborator Author

@pepeiborra tests are added and passing.

@pepeiborra
Copy link
Collaborator

Needs rebasing

@jneira
Copy link
Member

jneira commented Sep 11, 2020

Yeah, i moved part of exe/Main to src/Ide/Main and modules and lib dependencies related with plugins to the executable component in the .cabal file. The plugin list continue being in exe/Main.hs though.
I hope the rebase is relatively easy but feel free to ask any question.

@georgefst
Copy link
Collaborator

that usecase could be improved with a separate action that flattens foo = \x y z -> bar into foo x y z = bar

HLint will deal with that, whenever that plugin is completed.

@isovector
Copy link
Collaborator Author

Rebased and everything is a go. The stack files need some new additions, but I was hoping CI would tell me what changes need to happen. How can we get CI running on this PR?

Copy link
Collaborator

@wz1000 wz1000 left a comment

Choose a reason for hiding this comment

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

Thanks, great work here! Look forward to using it.

isovector and others added 3 commits September 16, 2020 00:22
[WIP] Add Skeleton for Tactic Plugin

Local bindings

[WIP] Add more to the code action provider

more cases for bindings

is it a hole?

Beginning of tactics machinery

tactics machinery

split out tactics machinery; finish porting tactics

Haddock for tactics machinery

Use a map for hypothesis

Better types on LocalBindings

render the result of running a tactic

Hypothesis from bindings

Sort types

mostSpecificSpan

Render

Actually add the tactic plugin :)

[WIP] Do stuff

slightly better span

better sorting for specific spans

Fix size

[WIP] It does the thing!!

Multiple tactic actions

Parenthesize if necessary

[WIP] Home on the 'Range'

destruct and homo

fix naming and parens

Cleanup Plugin Tactic

context dependent destruct and homo

Generalized interface

More composable

Remove TacticVariety

Haddock

Describe spooky monoidal behavior

Only look at actual holes

Auto if possible

debugging

Maybe grafting works now

Transformation works; tree doesnt

Remove debugging

Proper indentation and parenthesizing

Less fancy parenthesizing

Don't crash if we can't lookup things

Holes must start with an underscore

Haddock pass

Module restructuring

Fix the cabal file

Intros, and disable some of the unpolished tactics

Disable autoIfPossible

Fix stack.yaml

Respond to simple PR comments.

Get a proper dflags

WIP on a better bindings interface

Simplify dflags lookup and expose titles

Tactic tests

Add a few more tests

Cleanup imports

Haddock the tests

Rebase on ghcide HEAD (haskell#378)

* Rebase on top of ghcide HEAD

* use Development.IDE to trim imports

* Fix Eval plugin to use GhcSessionDeps

Use stale data in explicit imports lens (haskell#383)

This prevents the lenses from disappearing while editing, which causes lots of
unpleasant jumping

Create hls-plugin-api and move plugins to exe

Keep current version

Add hls-plugin-api component to cradle

Move exe modules to main library

Format .cabal files with `cabal-fmt --ident 2`

Restore ghcide ref

Fix cradles

Move tactic plugin

Almost there!

Get the tests running again

Empty commit for CI

Add refinery to stack

more stack woes

Duplicate

NoExt and less dependency on ghc

Cradle is necessary

bump ghcide submodule (haskell#396)

* Bump ghcide submodule

* Update stack descriptors

Co-authored-by: Pepe Iborra <[email protected]>

Update ghcide

Compute an interval map of what's in scope

Fix 'binding'
@wz1000
Copy link
Collaborator

wz1000 commented Sep 16, 2020

@isovector please review the last two commits.

@jneira
Copy link
Member

jneira commented Sep 16, 2020

Hi, could we keep the default stack.yaml with ghc-8.6.5?, please
ghc-8.8.2 is not reliable in Windows
Moreover, a new version of ghc-lib has just been released, fixing a bug building it.
Thanks!

@Anrock
Copy link
Contributor

Anrock commented Oct 5, 2020

Um, how do I trigger case splitting on argument like

foo :: EnumLike -> Bool
foo Enum1 = ...
foo Enum2 = ...

In neovim I don't have those nice light bulbs to see where code actions are available and I've tried

foo :: EnumLike -> Bool  -- no available actions on foo
foo _ = ... -- no available actions on underscore
foo :: EnumLike -> Bool -- no available actions on foo
foo = ... -- no available actions on foo here either

Where should I put my text cursor exactly to see case splitting or is it only for case of?

@wz1000
Copy link
Collaborator

wz1000 commented Oct 5, 2020

@Anrock try foo x = _

@Anrock
Copy link
Contributor

Anrock commented Oct 6, 2020

@wz1000 thanks, that works, however it produces (case x of ...) and I wanted multiple definitions of function. I guess this would be another feature.

@isovector
Copy link
Collaborator Author

pull requests welcome!

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.

Case splitting features
8 participants