Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Bugfix :: Support for 'use' on a nullable IDisposable #18262

Merged
merged 3 commits into from
Feb 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/release-notes/.FSharp.Compiler.Service/9.0.300.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

### Added
* Added missing type constraints in FCS. ([PR #18241](https://github.com/dotnet/fsharp/pull/18241))
* The 'use' keyword can be used on IDisposable|null without nullness warnings ([PR #18262](https://github.com/dotnet/fsharp/pull/18262))

### Changed

Expand Down
6 changes: 3 additions & 3 deletions src/Compiler/Checking/Expressions/CheckExpressions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3097,7 +3097,7 @@ let BuildDisposableCleanup (cenv: cenv) env m (v: Val) =
else
mkUnit g m
else
let disposeObjVar, disposeObjExpr = mkCompGenLocal m "objectToDispose" g.system_IDisposable_ty
let disposeObjVar, disposeObjExpr = mkCompGenLocal m "objectToDispose" g.system_IDisposableNull_ty
let disposeExpr, _ = BuildPossiblyConditionalMethodCall cenv env PossiblyMutates m false disposeMethod NormalValUse [] [disposeObjExpr] [] None
let inputExpr = mkCoerceExpr(exprForVal v.Range v, g.obj_ty_ambivalent, m, v.Type)
mkIsInstConditional g m g.system_IDisposable_ty inputExpr disposeObjVar disposeExpr (mkUnit g m)
Expand Down Expand Up @@ -6811,7 +6811,7 @@ and TcCtorCall isNaked cenv env tpenv (overallTy: OverallTy) objTy mObjTyOpt ite
match item, args with
| Item.CtorGroup(methodName, minfos), _ ->
let meths = List.map (fun minfo -> minfo, None) minfos
if isNaked && TypeFeasiblySubsumesType 0 g cenv.amap mWholeCall g.system_IDisposable_ty NoCoerce objTy then
if isNaked && TypeFeasiblySubsumesType 0 g cenv.amap mWholeCall g.system_IDisposableNull_ty NoCoerce objTy then
warning(Error(FSComp.SR.tcIDisposableTypeShouldUseNew(), mWholeCall))

// Check the type is not abstract
Expand Down Expand Up @@ -11603,7 +11603,7 @@ and TcLetBinding (cenv: cenv) isUse env containerInfo declKind tpenv (synBinds,
let isDiscarded = match checkedPat2 with TPat_wild _ -> true | _ -> false
let allValsDefinedByPattern = if isDiscarded then [patternInputTmp] else allValsDefinedByPattern
(allValsDefinedByPattern, (bodyExpr, bodyExprTy)) ||> List.foldBack (fun v (bodyExpr, bodyExprTy) ->
AddCxTypeMustSubsumeType ContextInfo.NoContext denv cenv.css v.Range NoTrace g.system_IDisposable_ty v.Type
AddCxTypeMustSubsumeType ContextInfo.NoContext denv cenv.css v.Range NoTrace g.system_IDisposableNull_ty v.Type
let cleanupE = BuildDisposableCleanup cenv env m v
mkTryFinally g (bodyExpr, cleanupE, m, bodyExprTy, DebugPointAtTry.No, DebugPointAtFinally.No), bodyExprTy)
else
Expand Down
2 changes: 1 addition & 1 deletion src/Compiler/Checking/Expressions/CheckExpressionsOps.fs
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ let mkSeqEmpty (cenv: TcFileState) env m genTy =

let mkSeqUsing (cenv: TcFileState) (env: TcEnv) m resourceTy genTy resourceExpr lam =
let g = cenv.g
AddCxTypeMustSubsumeType ContextInfo.NoContext env.DisplayEnv cenv.css m NoTrace g.system_IDisposable_ty resourceTy
AddCxTypeMustSubsumeType ContextInfo.NoContext env.DisplayEnv cenv.css m NoTrace g.system_IDisposableNull_ty resourceTy
let genResultTy = NewInferenceType g
UnifyTypes cenv env m genTy (mkSeqTy cenv.g genResultTy)
mkCallSeqUsing cenv.g m resourceTy genResultTy resourceExpr lam
Expand Down
2 changes: 1 addition & 1 deletion src/Compiler/Optimize/LowerComputedCollections.fs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ let BuildDisposableCleanup tcVal (g: TcGlobals) infoReader m (v: Val) =

disposeExpr
else
let disposeObjVar, disposeObjExpr = mkCompGenLocal m "objectToDispose" g.system_IDisposable_ty
let disposeObjVar, disposeObjExpr = mkCompGenLocal m "objectToDispose" g.system_IDisposableNull_ty
let disposeExpr, _ = BuildMethodCall tcVal g infoReader.amap PossiblyMutates m false disposeMethod NormalValUse [] [disposeObjExpr] [] None
let inputExpr = mkCoerceExpr(exprForVal v.Range v, g.obj_ty_ambivalent, m, v.Type)
mkIsInstConditional g m g.system_IDisposable_ty inputExpr disposeObjVar disposeExpr (mkUnit g m)
Expand Down
1 change: 1 addition & 0 deletions src/Compiler/TypedTree/TcGlobals.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1369,6 +1369,7 @@ type TcGlobals(
member val system_Array_ty = mkSysNonGenericTy sys "Array"
member val system_Object_ty = mkSysNonGenericTy sys "Object"
member val system_IDisposable_ty = mkSysNonGenericTy sys "IDisposable"
member val system_IDisposableNull_ty = mkNonGenericTyWithNullness (findSysTyconRef sys "IDisposable") v_knownWithNull
member val system_RuntimeHelpers_ty = mkSysNonGenericTy sysCompilerServices "RuntimeHelpers"
member val system_Value_ty = mkSysNonGenericTy sys "ValueType"
member val system_Delegate_ty = mkSysNonGenericTy sys "Delegate"
Expand Down
2 changes: 2 additions & 0 deletions src/Compiler/TypedTree/TcGlobals.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -1199,6 +1199,8 @@ type internal TcGlobals =

member system_IDisposable_ty: FSharp.Compiler.TypedTree.TType

member system_IDisposableNull_ty: FSharp.Compiler.TypedTree.TType

member system_IFormattable_tcref: FSharp.Compiler.TypedTree.EntityRef

member system_IFormattable_ty: FSharp.Compiler.TypedTree.TType
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,19 @@ let safeHolder : IDisposable =
|> typeCheckWithStrictNullness
|> shouldSucceed

[<Fact>]
let ``Can _use_ a nullable IDisposable`` () =
FSharp """module TestLib
open System
let workWithResource (getD:int -> (IDisposable|null)) =
use _ = getD 15
15

"""
|> asLibrary
|> typeCheckWithStrictNullness
|> shouldSucceed

[<Fact>]
let ``Does not duplicate warnings`` () =
FSharp """
Expand Down