Skip to content

Commit 2f45bd9

Browse files
authored
Merge branch 'main' into fix/aliasAs-type
2 parents e3adde3 + f963186 commit 2f45bd9

5 files changed

Lines changed: 336 additions & 28 deletions

File tree

src/Elm/Let.elm

Lines changed: 66 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ import Elm exposing (Expression)
142142
import Elm.Syntax.Expression as Exp
143143
import Elm.Syntax.Node as Node
144144
import Elm.Syntax.Pattern as Pattern
145+
import Elm.Syntax.TypeAnnotation as Annotation
145146
import Internal.Arg
146147
import Internal.Compiler as Compiler exposing (Module)
147148
import Internal.Index as Index
@@ -335,10 +336,13 @@ fn desiredName arg toInnerFn sourceLet =
335336
Elm.apply
336337
(Compiler.Expression
337338
(\_ ->
338-
{ innerFnDetails
339-
| expression =
340-
Exp.FunctionOrValue []
341-
name
339+
{ expression =
340+
Exp.FunctionOrValue [] name
341+
, annotation =
342+
letFnAnnotation
343+
[ argDetails.details.annotation ]
344+
innerFnDetails.annotation
345+
, imports = innerFnDetails.imports
342346
}
343347
)
344348
)
@@ -349,6 +353,45 @@ fn desiredName arg toInnerFn sourceLet =
349353
)
350354

351355

356+
{-| Build the function type annotation for a let-bound function's
357+
reference expression. Takes the arg annotations (in order) and the
358+
body's annotation, and produces `arg1 -> arg2 -> ... -> body`.
359+
360+
This is needed because `Elm.apply` needs a proper function type
361+
annotation to derive the return type when calling the let-bound
362+
function. Without this, the call would use the body's annotation
363+
directly, which is the return type rather than a function type.
364+
-}
365+
letFnAnnotation :
366+
List (Result (List Compiler.InferenceError) Compiler.Inference)
367+
-> Result (List Compiler.InferenceError) Compiler.Inference
368+
-> Result (List Compiler.InferenceError) Compiler.Inference
369+
letFnAnnotation argAnnotations bodyAnnotation =
370+
List.foldr
371+
(\argResult resultSoFar ->
372+
Result.map2
373+
(\argAnn soFar ->
374+
{ type_ =
375+
Annotation.FunctionTypeAnnotation
376+
(Compiler.nodify argAnn.type_)
377+
(Compiler.nodify soFar.type_)
378+
, inferences =
379+
Compiler.mergeInferences
380+
argAnn.inferences
381+
soFar.inferences
382+
, aliases =
383+
Compiler.mergeAliases
384+
argAnn.aliases
385+
soFar.aliases
386+
}
387+
)
388+
argResult
389+
resultSoFar
390+
)
391+
bodyAnnotation
392+
argAnnotations
393+
394+
352395
{-| -}
353396
fn2 :
354397
String
@@ -399,10 +442,15 @@ fn2 desiredName argOne argTwo toInnerFn sourceLet =
399442
Elm.apply
400443
(Compiler.Expression
401444
(\_ ->
402-
{ innerFnDetails
403-
| expression =
404-
Exp.FunctionOrValue []
405-
name
445+
{ expression =
446+
Exp.FunctionOrValue [] name
447+
, annotation =
448+
letFnAnnotation
449+
[ argOneDetails.details.annotation
450+
, argTwoDetails.details.annotation
451+
]
452+
innerFnDetails.annotation
453+
, imports = innerFnDetails.imports
406454
}
407455
)
408456
)
@@ -473,10 +521,16 @@ fn3 desiredName argOne argTwo argThree toInnerFn sourceLet =
473521
Elm.apply
474522
(Compiler.Expression
475523
(\_ ->
476-
{ innerFnDetails
477-
| expression =
478-
Exp.FunctionOrValue []
479-
name
524+
{ expression =
525+
Exp.FunctionOrValue [] name
526+
, annotation =
527+
letFnAnnotation
528+
[ argOneDetails.details.annotation
529+
, argTwoDetails.details.annotation
530+
, argThreeDetails.details.annotation
531+
]
532+
innerFnDetails.annotation
533+
, imports = innerFnDetails.imports
480534
}
481535
)
482536
)

src/Internal/Compiler.elm

Lines changed: 112 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1020,7 +1020,7 @@ resolve index cache annotation =
10201020
getRestrictions annotation cache
10211021
in
10221022
newAnnotation
1023-
|> rewriteTypeVariables
1023+
|> rewriteTypeVariables cache
10241024
|> checkRestrictions restrictions
10251025

10261026
Err err ->
@@ -1286,30 +1286,130 @@ getRestrictionsHelper existingRestrictions notation cache =
12861286
existingRestrictions
12871287

12881288

1289-
rewriteTypeVariables : Annotation.TypeAnnotation -> Annotation.TypeAnnotation
1290-
rewriteTypeVariables type_ =
1289+
{-| Rewrite type variable names to clean forms, preserving typeclass
1290+
constraint names.
1291+
1292+
When a constrained type variable (like `number_0`) gets resolved to
1293+
another generic variable (like `arg_0`), the constraint name is lost.
1294+
This function builds a mapping from resolved variable names back to
1295+
their constraint names, so `arg_0` gets renamed to `number` instead
1296+
of `a`.
1297+
-}
1298+
rewriteTypeVariables :
1299+
VariableCache
1300+
-> Annotation.TypeAnnotation
1301+
-> Annotation.TypeAnnotation
1302+
rewriteTypeVariables cache resolvedAnnotation =
12911303
let
1304+
-- Build a map from resolved generic names to constraint names.
1305+
-- Check BOTH directions:
1306+
-- 1. Forward: a constrained name (number_0) maps to a generic (arg_0)
1307+
-- 2. Reverse: a generic (arg_0) maps to a constrained name (comparable)
1308+
-- Case 2 happens with applyInfix operators that use fixed names
1309+
-- like "comparable" which then get unified with arg variables.
1310+
constraintOverrides : Dict String String
1311+
constraintOverrides =
1312+
Dict.foldl
1313+
(\key value acc ->
1314+
case value of
1315+
Annotation.GenericType resolvedName ->
1316+
let
1317+
keyRestriction =
1318+
nameToRestrictions key
1319+
in
1320+
case keyRestriction of
1321+
NoRestrictions ->
1322+
-- Key has no constraint, but maybe the
1323+
-- resolved name does (reverse direction)
1324+
let
1325+
resolvedRestriction =
1326+
nameToRestrictions resolvedName
1327+
in
1328+
case resolvedRestriction of
1329+
NoRestrictions ->
1330+
acc
1331+
1332+
_ ->
1333+
-- The target has a constraint — propagate
1334+
-- it to the key name
1335+
Dict.insert key
1336+
(restrictionToName resolvedRestriction)
1337+
acc
1338+
1339+
_ ->
1340+
-- Key has a constraint — propagate to resolved name
1341+
Dict.insert resolvedName
1342+
(restrictionToName keyRestriction)
1343+
acc
1344+
1345+
_ ->
1346+
acc
1347+
)
1348+
Dict.empty
1349+
cache
1350+
12921351
existing : Set String
12931352
existing =
1294-
getGenericsHelper type_
1353+
getGenericsHelper resolvedAnnotation
12951354
|> Set.fromList
12961355
in
1297-
Tuple.second (rewriteTypeVariablesHelper existing Dict.empty type_)
1356+
Tuple.second
1357+
(rewriteTypeVariablesHelper
1358+
constraintOverrides
1359+
existing
1360+
Dict.empty
1361+
resolvedAnnotation
1362+
)
1363+
12981364

1365+
restrictionToName : Restrictions -> String
1366+
restrictionToName restriction =
1367+
case restriction of
1368+
IsNumber ->
1369+
"number"
1370+
1371+
IsComparable ->
1372+
"comparable"
1373+
1374+
IsAppendable ->
1375+
"appendable"
1376+
1377+
IsAppendableComparable ->
1378+
"compappend"
12991379

1300-
rewriteTypeVariablesHelper : Set String -> Dict String String -> Annotation.TypeAnnotation -> ( Dict String String, Annotation.TypeAnnotation )
1301-
rewriteTypeVariablesHelper existing renames type_ =
1380+
_ ->
1381+
"a"
1382+
1383+
1384+
{-| Rewrite type variable names to clean, simplified forms.
1385+
1386+
The `overrides` dict maps variable names to constraint names
1387+
(e.g., "arg\_0" → "number") so that typeclass constraints are
1388+
preserved through the renaming process. Pass `Dict.empty` when
1389+
no constraint preservation is needed.
1390+
-}
1391+
rewriteTypeVariablesHelper :
1392+
Dict String String
1393+
-> Set String
1394+
-> Dict String String
1395+
-> Annotation.TypeAnnotation
1396+
-> ( Dict String String, Annotation.TypeAnnotation )
1397+
rewriteTypeVariablesHelper overrides existing renames type_ =
13021398
case type_ of
13031399
Annotation.GenericType varName ->
13041400
case Dict.get varName renames of
13051401
Nothing ->
13061402
let
13071403
simplified : String
13081404
simplified =
1309-
simplify varName
1405+
case Dict.get varName overrides of
1406+
Just constraintName ->
1407+
constraintName
1408+
1409+
Nothing ->
1410+
simplify varName
13101411
in
13111412
if Set.member simplified existing && varName /= simplified then
1312-
-- We would have collided with an existing generic name
13131413
( renames, Annotation.GenericType simplified )
13141414

13151415
else
@@ -1326,7 +1426,7 @@ rewriteTypeVariablesHelper existing renames type_ =
13261426
(\(Node _ typevar) ( varUsed, varList ) ->
13271427
let
13281428
( oneUsed, oneType ) =
1329-
rewriteTypeVariablesHelper existing varUsed typevar
1429+
rewriteTypeVariablesHelper overrides existing varUsed typevar
13301430
in
13311431
( oneUsed, nodify oneType :: varList )
13321432
)
@@ -1351,10 +1451,10 @@ rewriteTypeVariablesHelper existing renames type_ =
13511451
Annotation.FunctionTypeAnnotation (Node _ one) (Node _ two) ->
13521452
let
13531453
( oneUsed, oneType ) =
1354-
rewriteTypeVariablesHelper existing renames one
1454+
rewriteTypeVariablesHelper overrides existing renames one
13551455

13561456
( twoUsed, twoType ) =
1357-
rewriteTypeVariablesHelper existing oneUsed two
1457+
rewriteTypeVariablesHelper overrides existing oneUsed two
13581458
in
13591459
( twoUsed
13601460
, Annotation.FunctionTypeAnnotation

src/Internal/Write.elm

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1060,12 +1060,18 @@ prettyOperatorApplication aliases indent symbol dir (Node _ exprl) (Node _ exprr
10601060
Right ->
10611061
( prec + 1, prec )
10621062

1063+
-- Lambda expressions need explicit parentheses on either side
1064+
-- of operators. On the right: `a |> \x -> b` is ambiguous.
1065+
-- On the left: `\x -> x <| "hello"` absorbs <| into the body.
10631066
( left, breakLeft ) =
1064-
prettyExpressionInner aliases { precedence = lprec } indent exprl
1067+
case exprl of
1068+
LambdaExpression _ ->
1069+
prettyExpressionInner aliases { precedence = lprec } indent exprl
1070+
|> Tuple.mapFirst Pretty.parens
1071+
1072+
_ ->
1073+
prettyExpressionInner aliases { precedence = lprec } indent exprl
10651074

1066-
-- Lambda expressions on the right side of operators like |>
1067-
-- need explicit parentheses because `a |> \x -> b |> \y -> c`
1068-
-- is ambiguous — the second |> could be inside the lambda body.
10691075
( right, breakRight ) =
10701076
case exprr of
10711077
LambdaExpression _ ->

tests/OperatorPrecedence.elm

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,4 +133,20 @@ pipes =
133133
(Elm.fn (Elm.Arg.var "y") (\y -> y))
134134
|> Elm.Expect.renderedAs
135135
""""hello" |> (\\x -> x) |> (\\y -> y)"""
136+
, test "pipeLeft with lambda is parenthesized" <|
137+
\_ ->
138+
Elm.Op.pipeLeft
139+
(Elm.fn (Elm.Arg.var "x") (\x -> x))
140+
(Elm.string "hello")
141+
|> Elm.Expect.renderedAs
142+
"""(\\x -> x) <| "hello\""""
143+
, test "pipeLeft with lambda applied to complex expression" <|
144+
\_ ->
145+
Elm.Op.pipeLeft
146+
(Elm.fn (Elm.Arg.var "x")
147+
(\x -> Elm.Op.append x (Elm.string "!"))
148+
)
149+
(Elm.string "hello")
150+
|> Elm.Expect.renderedAs
151+
"""(\\x -> x ++ "!") <| "hello\""""
136152
]

0 commit comments

Comments
 (0)