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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Limit definition search to provided relativeTo #3194

Merged
merged 11 commits into from Jul 12, 2022
Merged

Conversation

ChrisPenner
Copy link
Contributor

@ChrisPenner ChrisPenner commented Jul 5, 2022

Overview

Names are really confusing, currently the 'show definition by name' endpoint constructs a names object relative to where it's supposed to be, but still includes 'fall-backs' for all external names. When you call to get a definition by name, the server returns all matches, and the frontend picks on and renders it.

Problems:

  • The current name-search isn't strictly scoped to the 'relativeTo', so sometimes the frontend renders a completely different definition than the one you asked for
  • In general names aren't optimal in rendered definitions

Fix

Additional changes:

  • Make sorting out names quicker by using the recently added namespace column on the names index.

Old view of trunk.Seq

The names we're getting here are actually from a completely different definition than the one we asked for 馃槵

image

New view of trunk.Seq from public.distributed

Here we can see that we still get some less-desirable names here, there are multiple Seq's in the current scope, collab.Seq and trunk.Seq are the same hash, but collab.Seq is preferred simply because it's earlier alphabetically.

I have an idea for fixing this problem too, but I'll split that into a separate PR if I can figure it out.

image

New view of Seq from public.distributed.trunk

Now that we're within the perspective of trunk all of the names are properly minimally prefixed and what we expect!

image

@ChrisPenner ChrisPenner changed the title Cp/abbreviate names Limit definition search to provided relativeTo Jul 5, 2022
@@ -274,7 +274,7 @@ suffixedTermName :: Int -> Referent -> NamesWithHistory -> [HQ'.HashQualified Na
isHQ'd = R.manyDom fqn rel -- it is conflicted
hq n = HQ'.take length (hq' n r)
hqn = if isHQ'd then hq n' else HQ'.fromName n'
in (isHQ'd, Name.countSegments fqn, Name.isAbsolute n', hqn)
in (isHQ'd, Name.countSegments n', Name.isAbsolute n', hqn)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Prefer shortest name by the abbreviated name, rather than the fully qualified name.

@@ -322,13 +322,11 @@ findShallowReadmeInBranchAndRender ::
Width ->
Rt.Runtime Symbol ->
Codebase IO Symbol Ann ->
NamesWithHistory ->
PPE.PrettyPrintEnvDecl ->
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This function only uses a PPE

else do
flip prettyAndParseNamesForBranch (AllNames path) <$> resolveCausalHash (Just bh) codebase

let localPPE = PPE.fromNamesDecl hashLen (NamesWithHistory.fromCurrentNames localNames)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Use the trick from #3172 to improve names chosen in PPE

termNames <- Q.rootTermNames
typeNames <- Q.rootTypeNames
pure (fmap (bimap s2cTextReferent (fmap s2cConstructorType)) <$> termNames, fmap s2cTextReference <$> typeNames)
rootNamesByPath ::
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Now that I have the namespace in sqlite it's easy to partition names in sqlite which is likely faster than doing it in Haskell.

PPE.fromNamesDecl hqLength (NamesWithHistory.fromCurrentNames printNames)

width =
(_parseNames, _printNames, localNamesOnly, ppe) <- scopedNamesForBranchHash codebase root path
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The biggest change in this function is that we previously used "prettyNames" for our initial term search which would erroneously fall-back to global names even though we're explicitly searching within a namespace.

Now any searches for scope-local things use localNamesOnly explicitly, and in cases where fallback is desired we use a smart PPE which does suffixification and fall-back correctly.

@ChrisPenner ChrisPenner self-assigned this Jul 6, 2022
@ChrisPenner ChrisPenner marked this pull request as ready for review July 6, 2022 23:53
-- E.g.
-- ["base.List.map", "base.Bag.map"] -> ["List.map", "Bag.map"]
-- ["base.List.filter", "base.List.map"] -> ["filter", "map"]
minimalUniqueSuffix :: Names -> Names
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 initially thought I would need this, but it's easier to handle within the PPE layer so this is currently unused.

I'd prefer to just leave it here since it may be useful in the future, but happy to remove it if it seems clutter-full

@@ -312,99 +312,11 @@ GET /api/getDefinition?names=%23qkhkl0n238&relativeTo=nested
},
"typeDefinitions": {}
}
-- Should find definitions by hash, using global names if no names in specified path.
-- Should filter out any definitions which aren't in the provided namespace even if the hash matches.
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 believe these old semantics were sub-optimal anyways, if you specifically search for a hash within a specific namespace, I think you should only get hashes that are in that namespace. If you want to do a global search for a hash you can just search at the namespace root.

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 need another PR which PREFERS local names by hash, but will fall back to global names, biased towards the current perspective.

This is so you can click on external names in a pretty-printed definition.

@pchiusano
Copy link
Member

Awesome @ChrisPenner

exactly what I鈥檇 hoped for.

I鈥檒l let other folks weigh in on the code changes, but the new behavior looks great!

@pchiusano pchiusano added this to the M4 milestone Jul 8, 2022
@pchiusano
Copy link
Member

@ChrisPenner just wanted to ask you about https://share-next.unison-lang.org/users/unison/code/latest/namespaces/public/base/main/;/terms/abilities/Random/shuffle

Notice that Random.splitmix is getting the name latest.Random.splitmix (same number of segments as main.Random.splitmix, but l comes before m)

image

Random.splitmix (in main, the current perspective) is the same as latest.Random.splitmix (not even in the current perspective)

I'm not sure if your other names-related PR addresses this (or if this one does but it's just not deployed yet), but we should never show a name for a definition from outside the current perspective if that definition has a name within the current perspective.

-- - 'local' includes ONLY the names within the provided path
-- - 'ppe' is a ppe which searches for a name within the path first, but falls back to a global name search.
-- The 'suffixified' component of this ppe will search for the shortest unambiguous suffix within the scope in which the name is found (local, falling back to global)
scopedNamesForBranchHash :: forall m v a. Monad m => Codebase m v a -> Maybe (Branch.CausalHash) -> Path -> Backend m (Names, Names, Names, PPE.PrettyPrintEnvDecl)
Copy link
Contributor

@aryairani aryairani Jul 10, 2022

Choose a reason for hiding this comment

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

Do we use the first two return values anywhere?

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 think we still need to compute them to make the PPE, but no I don't think I need to return them, I can change that 馃憤馃徏

@ChrisPenner
Copy link
Contributor Author

@pchiusano

Random.splitmix (in main, the current perspective) is the same as latest.Random.splitmix (not even in the current perspective)

Are you sure? I'm seeing different hashes for these:

image

I think the base.main doc just refers to the splitmix from base.latest by accident.

I do think I need to re-evaluate how we filter out 'hash-only' queries though 馃

@pchiusano
Copy link
Member

pchiusano commented Jul 11, 2022

Good call. You're right.

I just tracked down why these are different, and it looks like main of base replaced the implementation of toBytesLittleEndian to use a builtin.

I'll fixup the docs in base.

Update: this is done and looks good now: https://share-next.unison-lang.org/users/unison/code/latest/namespaces/public/base/main/;/terms/abilities/Random/shuffle

rootTermNamesByPath mayNamespace = do
let (namespace, subnamespace) = case mayNamespace of
Nothing -> ("", "*")
Just namespace -> (namespace, globEscape namespace <> ".*")
Copy link
Member

Choose a reason for hiding this comment

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

I wonder if we should escape namespace too, in case we go back to allowing things like Int.*.doc? In fact, I wonder if we should escape . characters in name segments for that reason, too.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

namespace isn't being passed to a GLOB, it's used in an equality comparison, so there's no need to escape it, or did you mean something else?

Nothing -> (mempty, [(n, ref)])
Just stripped -> ([(Name.makeRelative stripped, ref)], mempty)
Nothing -> error $ "Expected name to be in namespace" <> show (n, reverse reversedPathSegments)
Just stripped -> (Name.makeRelative stripped, ref)
Copy link
Member

Choose a reason for hiding this comment

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

I'm wondering if the Name.makeRelative is necessary here - aren't all of these names relative, even those that we put in absoluteExternalNames? Or - if they get converted to absolute names at some point - where does that happen?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

To be honest I think we have a lot of unnecessary work going on with names but it's extremely difficult to get my head around it and make too many changes at once without breaking everything and not knowing why haha.

I think what I envision is that actually names objects should probably just have all absolutely qualified names, then the PPE should make them relative and/or suffixify them when it's time to display them.

Copy link
Member

@mitchellwrosen mitchellwrosen left a comment

Choose a reason for hiding this comment

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

Approving with the disclaimer that I don't totally understand what's going on, even after the call 馃槄

My understanding is really just: things were very wrong before, now after a lot of changes, they are better, but not optimal.

@ChrisPenner ChrisPenner merged commit 6ab3865 into trunk Jul 12, 2022
@ChrisPenner ChrisPenner deleted the cp/abbreviate-names branch July 12, 2022 15:46
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

4 participants