Skip to content

Conversation

@pchiusano
Copy link
Member

@pchiusano pchiusano commented Oct 1, 2025

Previously, the pretty-printer would fail to insert a use clause at the start of a do block, if that do block began with a destructuring bind. This could sometimes lead to some terrible rendering with chains of operators all showing up with qualified names.

Here's a minimal example. There are two things called ++, a zoink.++ and foo.++. Thus, the code must disambiguate with a use clause:

a zoink.++ b = ()

a foo.++ b = "abracadabra"

example = do 
  use foo ++
  (x, y) = ("hi", "there")
  x ++ y ++ "z" ++ "a" ++ "b" ++ "c"

The use clause wasn't being used when viewing example because the do block starts with a Match, which isn't typically considered a candidate spot for use insertion. But actually, if the match happens to be a destructuring bind, putting a use clause there is fine.

Here's before and after -

Before:

CleanShot 2025-10-01 at 16 10 25@2x

After:

CleanShot 2025-10-01 at 16 10 48@2x

Testing

I added a transcript test and it passes all the existing transcript tests.

Misc

I fixed what ended up being an unrelated bug with Bytes literals causing imports of Bytes.fromList.impl.

Copy link
Member Author

@pchiusano pchiusano left a comment

Choose a reason for hiding this comment

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

Self review

Comment on lines 1358 to 1371
reannotateUp g t = case ABT.out t of
ABT.Var v -> ABT.annotatedVar (annotation t, g t) v
ABT.Cycle body ->
let body' = reannotateUp g body
in ABT.cycle' (annotation t, snd (annotation body')) body'
ABT.Abs v body ->
let body' = reannotateUp g body
in ABT.abs' (annotation t, snd (annotation body')) v body'
ABT.Tm body ->
-- literals like 0xsaaff don't contribute to the annotations
-- even though they desugar to function calls
let body' = reannotateUp g <$> body
ann = if isLiteral t then mempty else g t <> foldMap (snd . annotation) body'
in ABT.tm' (annotation t, ann) body'
Copy link
Member Author

Choose a reason for hiding this comment

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

This function didn't quite exist in ABT and it's too special purpose to add there since it's specific to the Unison term functor - nodes that are bytes literals have their annotation zero'd out to avoid contributing to use clauses that are inserted higher in the syntax tree.

Comment on lines -1889 to +1913
pattern Bytes' :: [Word64] -> Term3 v PrintAnnotation
pattern Bytes' :: [Word64] -> Term2 v at ap v a
pattern Bytes' bs <- (toBytes -> Just bs)

toBytes :: Term3 v PrintAnnotation -> Maybe [Word64]
toBytes :: Term2 v at ap v a -> Maybe [Word64]
Copy link
Member Author

Choose a reason for hiding this comment

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

Needed to generalize these signatures to get it to typecheck. They were needlessly monomorphic.

Comment on lines -320 to +341
Delay' x
| Match' _ _ <- x -> do
Delay' x@(Match' scrutinee cs)
| not (isDestructuringBind scrutinee cs) -> do
px <- pretty0 (ac Annotation Block im doc) x
let hang = if isSoftHangable x then PP.softHang else PP.hang
pure . paren (p > Control) $
fmt S.ControlKeyword "do" `hang` px
| otherwise -> do
let (im0', uses0) = calcImports im x
let allowUses = isLet x || (p == Bottom)
let im' = if allowUses then im0' else im
let uses = if allowUses then uses0 else []
let soft = isSoftHangable x && null uses && p < Annotation
let hang = if soft then PP.softHang else PP.hang
px <- pretty0 (ac Annotation Block im' doc) x
-- this makes sure we get proper indentation if `px` spills onto
-- multiple lines, since `do` introduces layout block
let indent = PP.Width (if soft then 2 else 0) + (if soft && p < Application then 1 else 0)
pure . paren (p > Control) $
fmt S.ControlKeyword "do" `hang` PP.lines (uses <> [PP.indentNAfterNewline indent px])
Delay' x -> do
let (im0', uses0) = calcImports im x
let allowUses = isLet x || (p == Bottom) || isDestructure x
where
isDestructure (Match' scrutinee cs) = isDestructuringBind scrutinee cs
isDestructure _ = False
let im' = if allowUses then im0' else im
let uses = if allowUses then uses0 else []
let soft = isSoftHangable x && null uses && p < Annotation
let hang = if soft then PP.softHang else PP.hang
px <- pretty0 (ac Annotation Block im' doc) x
-- this makes sure we get proper indentation if `px` spills onto
-- multiple lines, since `do` introduces layout block
let indent = PP.Width (if soft then 2 else 0) + (if soft && p < Application then 1 else 0)
pure . paren (p > Control) $
fmt S.ControlKeyword "do" `hang` PP.lines (uses <> [PP.indentNAfterNewline indent px])
Copy link
Member Author

Choose a reason for hiding this comment

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

This logic is finnicky but it is the same as before, except that we allow use insertion if the first thing in a do is a destructuring bind.

@pchiusano pchiusano requested a review from runarorama October 2, 2025 01:52
@runarorama runarorama merged commit 9c5d297 into trunk Oct 2, 2025
31 checks passed
@runarorama runarorama deleted the fix/use-insertion branch October 2, 2025 11:14
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.

3 participants