feat: extract delabAppCore, define withOverApp, and make over-applied projections pretty print (#3083)

To handle delaborating notations that are functions that can be applied
to arguments, extracts the core function application delaborator as a
separate function that accepts the number of arguments to process and a
delaborator to apply to the "head" of the expression.

Defines `withOverApp`, which has the same interface as the combinator of
the same name from std4, but it uses this core function application
delaborator.

Uses `withOverApp` to improve a number of application delaborators,
notably projections. This means Mathlib can stop using `pp_dot` for
structure fields that have function types.

Incidentally fixes `getParamKinds` to specialize default values to use
supplied arguments, which impacts how default arguments are delaborated.

---------

Co-authored-by: Sebastian Ullrich <sebasti@nullri.ch>
This commit is contained in:
Kyle Miller 2024-01-10 08:24:28 -05:00 committed by GitHub
parent 9069c538ad
commit c394a834c3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 265 additions and 103 deletions

View file

@ -919,6 +919,17 @@ private def getAppNumArgsAux : Expr → Nat → Nat
def getAppNumArgs (e : Expr) : Nat :=
getAppNumArgsAux e 0
/--
Like `Lean.Expr.getAppFn` but assumes the application has up to `maxArgs` arguments.
If there are any more arguments than this, then they are returned by `getAppFn` as part of the function.
In particular, if the given expression is a sequence of function applications `f a₁ .. aₙ`,
returns `f a₁ .. aₖ` where `k` is minimal such that `n - k ≤ maxArgs`.
-/
def getBoundedAppFn : (maxArgs : Nat) → Expr → Expr
| maxArgs' + 1, .app f _ => getBoundedAppFn maxArgs' f
| _, e => e
private def getAppArgsAux : Expr → Array Expr → Nat → Array Expr
| app f a, as, i => getAppArgsAux f (as.set! i a) (i-1)
| _, as, _ => as
@ -929,6 +940,21 @@ private def getAppArgsAux : Expr → Array Expr → Nat → Array Expr
let nargs := e.getAppNumArgs
getAppArgsAux e (mkArray nargs dummy) (nargs-1)
private def getBoundedAppArgsAux : Expr → Array Expr → Nat → Array Expr
| app f a, as, i + 1 => getBoundedAppArgsAux f (as.set! i a) i
| _, as, _ => as
/--
Like `Lean.Expr.getAppArgs` but returns up to `maxArgs` arguments.
In particular, given `f a₁ a₂ ... aₙ`, returns `#[aₖ₊₁, ..., aₙ]`
where `k` is minimal such that the size of this array is at most `maxArgs`.
-/
@[inline] def getBoundedAppArgs (maxArgs : Nat) (e : Expr) : Array Expr :=
let dummy := mkSort levelZero
let nargs := min maxArgs e.getAppNumArgs
getBoundedAppArgsAux e (mkArray nargs dummy) nargs
private def getAppRevArgsAux : Expr → Array Expr → Array Expr
| app f a, as => getAppRevArgsAux f (as.push a)
| _, as => as

View file

@ -104,72 +104,94 @@ def delabAppFn : Delab := do
else
delab
/--
A structure that records details of a function parameter.
-/
structure ParamKind where
/-- Binder name for the parameter. -/
name : Name
/-- Binder info for the parameter. -/
bInfo : BinderInfo
/-- The default value for the parameter, if the parameter has a default value. -/
defVal : Option Expr := none
/-- Whether the parameter is an autoparam (i.e., whether it uses a tactic for the default value). -/
isAutoParam : Bool := false
/--
Returns true if the parameter is an explicit parameter that has neither a default value nor a tactic.
-/
def ParamKind.isRegularExplicit (param : ParamKind) : Bool :=
param.bInfo.isExplicit && !param.isAutoParam && param.defVal.isNone
/-- Return array with n-th element set to kind of n-th parameter of `e`. -/
partial def getParamKinds : DelabM (Array ParamKind) := do
let e ← getExpr
/--
Given a function `f` supplied with arguments `args`, returns an array whose n-th element
is set to the kind of the n-th argument's associated parameter.
We do not assume the expression `mkAppN f args` is sensical,
and this function captures errors (except for panics) and returns the empty array.
The returned array might be longer than the number of arguments.
It gives parameter kinds for the fully-applied function.
Note: the `defVal` expressions are only guaranteed to be valid for parameters associated to the supplied arguments;
after this, they might refer to temporary fvars.
This function properly handles "overapplied" functions.
For example, while `id` takes one explicit argument, it can take more than one explicit
argument when its arguments are specialized to function types, like in `id id 2`.
-/
def getParamKinds (f : Expr) (args : Array Expr) : MetaM (Array ParamKind) := do
try
withTransparency TransparencyMode.all do
forallTelescopeArgs e.getAppFn e.getAppArgs fun params _ => do
params.mapM fun param => do
let l ← param.fvarId!.getDecl
pure { name := l.userName, bInfo := l.binderInfo, defVal := l.type.getOptParamDefault?, isAutoParam := l.type.isAutoParam }
let mut result : Array ParamKind := Array.mkEmpty args.size
let mut fnType ← inferType f
let mut j := 0
for i in [0:args.size] do
unless fnType.isForall do
fnType ← whnf (fnType.instantiateRevRange j i args)
j := i
let .forallE n t b bi := fnType | failure
let defVal := t.getOptParamDefault? |>.map (·.instantiateRevRange j i args)
result := result.push { name := n, bInfo := bi, defVal, isAutoParam := t.isAutoParam }
fnType := b
fnType := fnType.instantiateRevRange j args.size args
-- We still want to consider parameters past the ones for the supplied arguments.
forallTelescopeReducing fnType fun xs _ => do
xs.foldlM (init := result) fun result x => do
let l ← x.fvarId!.getDecl
-- Warning: the defVal might refer to fvars that are only valid in this context
pure <| result.push { name := l.userName, bInfo := l.binderInfo, defVal := l.type.getOptParamDefault?, isAutoParam := l.type.isAutoParam }
catch _ => pure #[] -- recall that expr may be nonsensical
where
forallTelescopeArgs f args k := do
forallBoundedTelescope (← inferType f) args.size fun xs b =>
if xs.isEmpty || xs.size == args.size then
-- we still want to consider optParams
forallTelescopeReducing b fun ys b => k (xs ++ ys) b
else
forallTelescopeArgs (mkAppN f $ args.shrink xs.size) (args.extract xs.size args.size) fun ys b =>
k (xs ++ ys) b
@[builtin_delab app]
def delabAppExplicit : Delab := do
let paramKinds ← getParamKinds
let tagAppFn ← getPPOption getPPTagAppFns
let (fnStx, _, argStxs) ← withAppFnArgs
(do
let stx ← withOptionAtCurrPos `pp.tagAppFns tagAppFn delabAppFn
let needsExplicit := stx.raw.getKind != ``Lean.Parser.Term.explicit
let stx ← if needsExplicit then `(@$stx) else pure stx
pure (stx, paramKinds.toList, #[]))
(fun ⟨fnStx, paramKinds, argStxs⟩ => do
let isInstImplicit := match paramKinds with
| [] => false
| param :: _ => param.bInfo == BinderInfo.instImplicit
let argStx ← if ← getPPOption getPPAnalysisHole then `(_)
else if isInstImplicit == true then
let stx ← if ← getPPOption getPPInstances then delab else `(_)
if ← getPPOption getPPInstanceTypes then
let typeStx ← withType delab
`(($stx : $typeStx))
else pure stx
else delab
pure (fnStx, paramKinds.tailD [], argStxs.push argStx))
return Syntax.mkApp fnStx argStxs
def shouldShowMotive (motive : Expr) (opts : Options) : MetaM Bool := do
pure (getPPMotivesAll opts)
<||> (pure (getPPMotivesPi opts) <&&> returnsPi motive)
<||> (pure (getPPMotivesNonConst opts) <&&> isNonConstFun motive)
def isRegularApp : DelabM Bool := do
/--
Returns true if an application should use explicit mode when delaborating.
-/
def useAppExplicit (paramKinds : Array ParamKind) : DelabM Bool := do
if ← getPPOption getPPExplicit then
if paramKinds.any (fun param => !param.isRegularExplicit) then return true
-- If the expression has an implicit function type, fall back to delabAppExplicit.
-- This is e.g. necessary for `@Eq`.
let isImplicitApp ← try
let ty ← whnf (← inferType (← getExpr))
pure <| ty.isForall && (ty.binderInfo matches .implicit | .instImplicit)
catch _ => pure false
if isImplicitApp then return true
return false
/--
Returns true if the application is a candidate for unexpanders.
-/
def isRegularApp (maxArgs : Nat) : DelabM Bool := do
let e ← getExpr
if not (unfoldMDatas e.getAppFn).isConst then return false
if ← withNaryFn (withMDatasOptions (getPPOption getPPUniverses <||> getPPOption getPPAnalysisBlockImplicit)) then return false
for i in [:e.getAppNumArgs] do
if ← withNaryArg i (getPPOption getPPAnalysisNamedArg) then return false
return true
if not (unfoldMDatas (e.getBoundedAppFn maxArgs)).isConst then return false
withBoundedAppFnArgs maxArgs
(not <$> withMDatasOptions (getPPOption getPPUniverses <||> getPPOption getPPAnalysisBlockImplicit))
(fun b => pure b <&&> not <$> getPPOption getPPAnalysisNamedArg)
def unexpandRegularApp (stx : Syntax) : Delab := do
let Expr.const c .. := (unfoldMDatas (← getExpr).getAppFn) | unreachable!
@ -204,25 +226,42 @@ def unexpandStructureInstance (stx : Syntax) : Delab := whenPPOption getPPStruct
if (← getPPOption getPPStructureInstanceType) then delab >>= pure ∘ some else pure none
`({ $fields,* $[: $tyStx]? })
@[builtin_delab app]
def delabAppImplicit : Delab := do
/--
Delaborates a function application in explicit mode, and ensures the resulting
head syntax is wrapped with `@`.
-/
def delabAppExplicitCore (maxArgs : Nat) (delabHead : Delab) (paramKinds : Array ParamKind) (tagAppFn : Bool) : Delab := do
let (fnStx, _, argStxs) ← withBoundedAppFnArgs maxArgs
(do
let stx ← withOptionAtCurrPos `pp.tagAppFns tagAppFn delabHead
let needsExplicit := stx.raw.getKind != ``Lean.Parser.Term.explicit
let stx ← if needsExplicit then `(@$stx) else pure stx
pure (stx, paramKinds.toList, #[]))
(fun ⟨fnStx, paramKinds, argStxs⟩ => do
let isInstImplicit := match paramKinds with
| [] => false
| param :: _ => param.bInfo == BinderInfo.instImplicit
let argStx ← if ← getPPOption getPPAnalysisHole then `(_)
else if isInstImplicit == true then
let stx ← if ← getPPOption getPPInstances then delab else `(_)
if ← getPPOption getPPInstanceTypes then
let typeStx ← withType delab
`(($stx : $typeStx))
else pure stx
else delab
pure (fnStx, paramKinds.tailD [], argStxs.push argStx))
return Syntax.mkApp fnStx argStxs
/--
Delaborates a function application in the standard mode, where implicit arguments are generally not
included.
-/
def delabAppImplicitCore (maxArgs : Nat) (delabHead : Delab) (paramKinds : Array ParamKind) (tagAppFn : Bool) : Delab := do
-- TODO: always call the unexpanders, make them guard on the right # args?
let paramKinds ← getParamKinds
if ← getPPOption getPPExplicit then
if paramKinds.any (fun param => !param.isRegularExplicit) then failure
-- If the application has an implicit function type, fall back to delabAppExplicit.
-- This is e.g. necessary for `@Eq`.
let isImplicitApp ← try
let ty ← whnf (← inferType (← getExpr))
pure <| ty.isForall && (ty.binderInfo == BinderInfo.implicit || ty.binderInfo == BinderInfo.instImplicit)
catch _ => pure false
if isImplicitApp then failure
let tagAppFn ← getPPOption getPPTagAppFns
let (fnStx, _, argStxs) ← withAppFnArgs
(withOptionAtCurrPos `pp.tagAppFns tagAppFn <|
return (← delabAppFn, paramKinds.toList, #[]))
let (fnStx, _, argStxs) ← withBoundedAppFnArgs maxArgs
(do
let stx ← withOptionAtCurrPos `pp.tagAppFns tagAppFn delabHead
return (stx, paramKinds.toList, #[]))
(fun (fnStx, paramKinds, argStxs) => do
let arg ← getExpr
let opts ← getOptions
@ -247,12 +286,60 @@ def delabAppImplicit : Delab := do
pure (fnStx, paramKinds.tailD [], argStxs))
let stx := Syntax.mkApp fnStx argStxs
if ← isRegularApp then
if ← isRegularApp maxArgs then
(guard (← getPPOption getPPNotation) *> unexpandRegularApp stx)
<|> (guard (← getPPOption getPPStructureInstances) *> unexpandStructureInstance stx)
<|> pure stx
else pure stx
/--
Delaborates applications. Removes up to `maxArgs` arguments to form
the "head" of the application and delaborates the head using `delabHead`.
The remaining arguments are processed depending on whether heuristics indicate that the application
should be delaborated using `@`.
-/
def delabAppCore (maxArgs : Nat) (delabHead : Delab) : Delab := do
let tagAppFn ← getPPOption getPPTagAppFns
let e ← getExpr
let paramKinds ← getParamKinds (e.getBoundedAppFn maxArgs) (e.getBoundedAppArgs maxArgs)
let useExplicit ← useAppExplicit paramKinds
if useExplicit then
delabAppExplicitCore maxArgs delabHead paramKinds tagAppFn
else
delabAppImplicitCore maxArgs delabHead paramKinds tagAppFn
/--
Default delaborator for applications.
-/
@[builtin_delab app]
def delabApp : Delab := do
delabAppCore (← getExpr).getAppNumArgs delabAppFn
/--
The `withOverApp` combinator allows delaborators to handle "over-application" by using the core
application delaborator to handle additional arguments.
For example, suppose `f : {A : Type} → Foo A → A` and we want to implement a delaborator for
applications `f x` to pretty print as `F[x]`. Because `A` is a type variable we might encounter
a term of the form `@f (A → B) x y`, which has an additional argument `y`.
With this combinator one can use an arity-2 delaborator to pretty print this as `F[x] y`.
* `arity`: the expected number of arguments to `f`.
The combinator will fail if fewer than this number of arguments are passed,
and if more than this number of arguments are passed the arguments are handled using
the standard application delaborator.
* `x`: constructs data corresponding to the main application (`f x` in the example)
-/
def withOverApp (arity : Nat) (x : Delab) : Delab := do
let n := (← getExpr).getAppNumArgs
guard <| n ≥ arity
if n == arity then
x
else
delabAppCore (n - arity) x
/-- State for `delabAppMatch` and helpers. -/
structure AppMatchState where
info : MatcherInfo
@ -394,25 +481,12 @@ def delabAppMatch : Delab := whenPPOption getPPNotation <| whenPPOption getPPMat
`(match $[$st.discrs:matchDiscr],* with $[| $pats,* => $st.rhss]*)
return Syntax.mkApp stx st.moreArgs
/--
Annotation key to use in hack for overapplied `let_fun` notation.
-/
def delabLetFunKey : Name := `_delabLetFun
/--
Delaborates applications of the form `letFun v (fun x => b)` as `let_fun x := v; b`.
-/
@[builtin_delab app.letFun]
def delabLetFun : Delab := whenPPOption getPPNotation do
def delabLetFun : Delab := whenPPOption getPPNotation <| withOverApp 4 do
let e ← getExpr
let nargs := e.getAppNumArgs
if 4 < nargs then
-- It's overapplied. Hack: insert metadata around the first four arguments and delaborate again.
-- This will cause the app delaborator to delaborate `(letFun v f) x1 ... xn` with `letFun v f` as the function.
let args := e.getAppArgs
let f := mkAppN e.getAppFn (args.extract 0 4)
let e' := mkAppN (mkAnnotation delabLetFunKey f) (args.extract 4 args.size)
return (← withTheReader SubExpr ({· with expr := e'}) delab)
guard <| e.getAppNumArgs == 4
let Expr.lam n _ b _ := e.appArg! | failure
let n ← getUnusedName n b
@ -611,13 +685,13 @@ def delabLit : Delab := do
-- `@OfNat.ofNat _ n _` ~> `n`
@[builtin_delab app.OfNat.ofNat]
def delabOfNat : Delab := whenPPOption getPPCoercions do
def delabOfNat : Delab := whenPPOption getPPCoercions <| withOverApp 3 do
let .app (.app _ (.lit (.natVal n))) _ ← getExpr | failure
return quote n
-- `@OfDecimal.ofDecimal _ _ m s e` ~> `m*10^(sign * e)` where `sign == 1` if `s = false` and `sign = -1` if `s = true`
@[builtin_delab app.OfScientific.ofScientific]
def delabOfScientific : Delab := whenPPOption getPPCoercions do
def delabOfScientific : Delab := whenPPOption getPPCoercions <| withOverApp 5 do
let expr ← getExpr
guard <| expr.getAppNumArgs == 5
let .lit (.natVal m) ← pure (expr.getArg! 2) | failure
@ -654,24 +728,23 @@ def delabProj : Delab := do
/-- Delaborate a call to a projection function such as `Prod.fst`. -/
@[builtin_delab app]
def delabProjectionApp : Delab := whenPPOption getPPStructureProjections $ do
let e@(Expr.app fn _) ← getExpr | failure
let Expr.app fn _ ← getExpr | failure
let .const c@(.str _ f) _ ← pure fn.getAppFn | failure
let env ← getEnv
let some info ← pure $ env.getProjectionFnInfo? c | failure
-- can't use with classes since the instance parameter is implicit
guard $ !info.fromClass
-- projection function should be fully applied (#struct params + 1 instance parameter)
-- TODO: support over-application
guard $ e.getAppNumArgs == info.numParams + 1
-- If pp.explicit is true, and the structure has parameters, we should not
-- use field notation because we will not be able to see the parameters.
let expl ← getPPOption getPPExplicit
guard $ !expl || info.numParams == 0
let appStx ← withAppArg delab
`($(appStx).$(mkIdent f):ident)
-- projection function should be fully applied (#struct params + 1 instance parameter)
withOverApp (info.numParams + 1) do
let appStx ← withAppArg delab
`($(appStx).$(mkIdent f):ident)
@[builtin_delab app.dite]
def delabDIte : Delab := whenPPOption getPPNotation do
def delabDIte : Delab := whenPPOption getPPNotation <| withOverApp 5 do
-- Note: we keep this as a delaborator for now because it actually accesses the expression.
guard $ (← getExpr).getAppNumArgs == 5
let c ← withAppFn $ withAppFn $ withAppFn $ withAppArg delab
@ -688,7 +761,7 @@ where
return (← delab, h.getId)
@[builtin_delab app.cond]
def delabCond : Delab := whenPPOption getPPNotation do
def delabCond : Delab := whenPPOption getPPNotation <| withOverApp 4 do
guard $ (← getExpr).getAppNumArgs == 4
let c ← withAppFn $ withAppFn $ withAppArg delab
let t ← withAppFn $ withAppArg delab

View file

@ -42,12 +42,28 @@ def withAppArg (x : m α) : m α := do descend (← getExpr).appArg! 1 x
def withType (x : m α) : m α := do
descend (← Meta.inferType (← getExpr)) Pos.typeCoord x -- phantom positions for types
/--
Uses `xa` to compute the fold across the arguments of an application,
where `xf` provides the initial value and is evaluated in the context of the head of the application.
-/
partial def withAppFnArgs (xf : m α) (xa : α → m α) : m α := do
if (← getExpr).isApp then
let acc ← withAppFn (withAppFnArgs xf xa)
withAppArg (xa acc)
else xf
/--
Uses `xa` to compute the fold across up to `maxArgs` outermost arguments of an application,
where `xf` provides the initial value and is evaluated in the context of the application minus
the arguments being folded across.
-/
def withBoundedAppFnArgs (maxArgs : Nat) (xf : m α) (xa : α → m α) : m α := do
match maxArgs, (← getExpr) with
| maxArgs' + 1, .app .. =>
let acc ← withAppFn (withBoundedAppFnArgs maxArgs' xf xa)
withAppArg (xa acc)
| _, _ => xf
def withBindingDomain (x : m α) : m α := do descend (← getExpr).bindingDomain! 0 x
def withBindingBody (n : Name) (x : m α) : m α := do

View file

@ -80,7 +80,7 @@ def ppExprTagged (e : Expr) (explicit : Bool := false) : MetaM CodeWithInfos :=
if explicit then
withOptionAtCurrPos pp.tagAppFns.name true do
withOptionAtCurrPos pp.explicit.name true do
delabAppImplicit <|> delabAppExplicit
delabApp
else
delab
let ⟨fmt, infos⟩ ← PrettyPrinter.ppExprWithInfos e (delab := delab)

View file

@ -1,7 +1,7 @@
283.lean:1:24-1:25: error: application type mismatch
f (f ?m)
f f
argument
f ?m
f
has type
?m : Sort ?u
but is expected to have type

View file

@ -1,20 +1,20 @@
fn.imp : {p : P} → Bar.fn p
439.lean:18:7-18:12: error: function expected at
Fn.imp fn
fn.imp
term has type
Bar.fn ?m
439.lean:29:7-29:11: error: function expected at
Fn.imp fn
fn.imp
term has type
Bar.fn ?m
Fn.imp fn : Bar.fn p
Fn.imp fn' Bp : Bar.fn p
fn.imp : Bar.fn p
fn'.imp Bp : Bar.fn p
439.lean:39:11-39:12: error: application type mismatch
Fn.imp fn' p
fn'.imp p
argument
p
has type
P : Sort u
but is expected to have type
Bar.fn ?m : Sort ?u
Fn.imp fn' (sorryAx (Bar.fn ?m) true) : Bar.fn ?m
fn'.imp (sorryAx (Bar.fn ?m) true) : Bar.fn ?m

39
tests/lean/delabApp.lean Normal file
View file

@ -0,0 +1,39 @@
/-!
# Testing features of the app delaborator, including overapplication
-/
/-!
Check that the optional value equality check is specialized to the supplied arguments
(rather than, formerly, the locally-defined fvars from a telescope).
-/
def foo (α : Type) [Inhabited α] (x : α := default) : α := x
#check foo Nat
#check foo Nat 1
/-!
Check that overapplied projections pretty print using projection notation.
-/
structure Foo where
f : Nat → Nat
#check ∀ (x : Foo), x.f 1 = 0
/-!
Overapplied `letFun`
-/
#check (have f := id; f) 1
/-!
Overapplied `OfNat.ofNat`
-/
instance : OfNat (Nat → Nat) 1 where
ofNat := id
#check (1 : Nat → Nat) 2
/-!
Overapplied `dite`
-/
#check (if h : True then id else id) 1

View file

@ -0,0 +1,8 @@
foo Nat : Nat
foo Nat 1 : Nat
∀ (x : Foo), x.f 1 = 0 : Prop
(let_fun f := id;
f)
1 : Nat
1 2 : Nat
(if h : True then id else id) 1 : Nat

View file

@ -9,4 +9,4 @@ fun (a : Nat) =>
(@Eq.{1} Bool (@bne.{0} Nat instBEqNat a (@OfNat.ofNat.{0} Nat 1 (instOfNatNat 1))) Bool.true)
(@Eq.{1} Bool (@bne.{0} Nat instBEqNat a (@OfNat.ofNat.{0} Nat 2 (instOfNatNat 2))) Bool.true)
def s : Option Nat :=
HOrElse.hOrElse (ConstantFunction.f myFun 3) fun x => ConstantFunction.f myFun 4
HOrElse.hOrElse (myFun.f 3) fun x => myFun.f 4

View file

@ -4,7 +4,7 @@ fun x x_1 =>
(fun x f x_2 =>
(match x_2, x with
| a, Nat.zero => fun x => a
| a, Nat.succ b => fun x => Nat.succ (PProd.fst x.fst a))
| a, Nat.succ b => fun x => Nat.succ (x.fst.fst a))
f)
x
protected def Nat.add : Nat → Nat → Nat :=
@ -13,7 +13,7 @@ fun x x_1 =>
(fun x f x_2 =>
(match (motive := Nat → (x : Nat) → Nat.below (motive := fun x => Nat → Nat) x → Nat) x_2, x with
| a, Nat.zero => fun x => a
| a, Nat.succ b => fun x => Nat.succ (PProd.fst x.fst a))
| a, Nat.succ b => fun x => Nat.succ (x.fst.fst a))
f)
x
theorem ex.{u} : ∀ {α β : Sort u} (h : α = β) (a : α), HEq (cast h a) a :=

View file

@ -1,5 +1,5 @@
sorryAtError.lean:13:46-13:47: error: application type mismatch
Ty.ty ty Γ
ty.ty Γ
argument
Γ
has type