Skip to content
Open
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/11.0.100.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
* Debug: rework for expressions stepping ([PR #19894](https://github.com/dotnet/fsharp/pull/19894))
* Debug: rework conditional erasure, fix stepping over literals ([PR #19897](https://github.com/dotnet/fsharp/pull/19897))
* Debug: fix if and match condition sequence points ([PR #19932](https://github.com/dotnet/fsharp/pull/19932))
* Support common types of `NotNullIfNotNullAttribute` usage. If a method parameter is marked with `NotNullIfNotNullAttribute`, the compiler will now honor this attribute and mark the return type as non-null. ([PR #19977](https://github.com/dotnet/fsharp/pull/19977))

### Changed

Expand Down
1 change: 1 addition & 0 deletions docs/release-notes/.Language/preview.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* Warn (FS3884) when a function or delegate value is used as an interpolated string argument, since it will be formatted via `ToString` rather than being applied. ([PR #19289](https://github.com/dotnet/fsharp/pull/19289))
* Added `MethodOverloadsCache` language feature (preview) that caches overload resolution results for repeated method calls, significantly improving compilation performance. ([PR #19072](https://github.com/dotnet/fsharp/pull/19072))
* Added `ErrorOnMissingSignatureAttribute` preview language feature: makes FS3888 (compiler-semantic attribute on the `.fs` but not on the `.fsi`) an error instead of a warning. ([Issue #19560](https://github.com/dotnet/fsharp/issues/19560), [PR #19880](https://github.com/dotnet/fsharp/pull/19880))
* Support common types of `NotNullIfNotNullAttribute` usage. If a method parameter is marked with `NotNullIfNotNullAttribute`, the compiler will now honor this attribute and mark the return type as non-null. ([PR #19977](https://github.com/dotnet/fsharp/pull/19977))

### Fixed

Expand Down
1 change: 1 addition & 0 deletions src/Compiler/AbstractIL/il.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1257,6 +1257,7 @@ type WellKnownILAttributes =
| RequiredMemberAttribute = (1u <<< 22)
| NullableContextAttribute = (1u <<< 23)
| AttributeUsageAttribute = (1u <<< 24)
| NotNullIfNotNullAttribute = (1u <<< 25)
| NotComputed = (1u <<< 31)

type internal ILAttributesStoredRepr =
Expand Down
1 change: 1 addition & 0 deletions src/Compiler/AbstractIL/il.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -907,6 +907,7 @@ type WellKnownILAttributes =
| RequiredMemberAttribute = (1u <<< 22)
| NullableContextAttribute = (1u <<< 23)
| AttributeUsageAttribute = (1u <<< 24)
| NotNullIfNotNullAttribute = (1u <<< 25)
| NotComputed = (1u <<< 31)

/// Represents the efficiency-oriented storage of ILAttributes in another item.
Expand Down
77 changes: 76 additions & 1 deletion src/Compiler/Checking/Expressions/CheckExpressions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3330,6 +3330,46 @@ let GetMethodArgs arg =

unnamedCallerArgs, namedCallerArgs

let NotNullIfNotNullParamNames g (minfo: MethInfo) =
match minfo with
| ILMeth(ilMethInfo = ilminfo) when ilminfo.RawMetadata.Return.CustomAttrsStored.HasWellKnownAttribute (g, WellKnownILAttributes.NotNullIfNotNullAttribute) ->
ilminfo.RawMetadata.Return.CustomAttrs.AsArray()
|> Array.toList
|> List.choose (fun attr ->
if classifyILAttrib attr &&& WellKnownILAttributes.NotNullIfNotNullAttribute <> WellKnownILAttributes.None then
match decodeILAttribData attr with
| [ ILAttribElem.String (Some paramName) ], _ -> Some paramName
| _ -> None
else
None)
| FSMeth(valRef = vref) ->
match vref.ValReprInfo with
| Some (ValReprInfo(result = retInfo)) when ArgReprInfoHasWellKnownAttribute g WellKnownValAttributes.NotNullIfNotNullAttribute retInfo ->
retInfo.Attribs.AsList()
|> List.choose (fun attrib ->
if classifyValAttrib g attrib &&& WellKnownValAttributes.NotNullIfNotNullAttribute <> WellKnownValAttributes.None then
match attrib with
| Attrib(unnamedArgs = [ AttribStringArg paramName ]) -> Some paramName
| _ -> None
else
None)
| _ -> []
| _ -> []

// Resolve the caller argument bound to 'paramName' and return the type of its type-checked expression.
let TryGetCallerArgType g (minfo: MethInfo) (callerArgs: CallerArgs<_>) paramName =
// First try to find a named argument with the given name
callerArgs.Named
|> List.tryPick (List.tryPick (fun (CallerNamedArg(id, arg)) -> if id.idText = paramName then Some arg else None))
|> Option.orElseWith (fun () ->
// If there is no matching named argument, find the argument in the same position as the parameter with the given name
minfo.GetParamNames()
|> Seq.concat
|> Seq.tryFindIndex (fun nm -> match nm with Some nm -> nm = paramName | _ -> false)
|> Option.bind (fun idx -> Seq.concat callerArgs.Unnamed |> Seq.tryItem idx)
)
|> Option.map (fun arg -> tyOfExpr g arg.Expr)

//-------------------------------------------------------------------------
// Helpers dealing with sequence expressions
//-------------------------------------------------------------------------
Expand Down Expand Up @@ -10270,12 +10310,26 @@ and TcMethodApplication_UniqueOverloadInference

let arityFilteredCandidates = candidateMethsAndProps

let makeOneCalledMeth (minfo, pinfoOpt, usesParamArrayConversion) =
let makeOneCalledMeth (minfo: MethInfo, pinfoOpt, usesParamArrayConversion) =
let minst = FreshenMethInfo mItem minfo
let callerTyArgs =
match tyArgsOpt with
| Some tyargs -> minfo.AdjustUserTypeInstForFSharpStyleIndexedExtensionMembers tyargs
| None -> minst

// If the return value is [<NotNullIfNotNull>], give the return a fresh nullness inference variable here so that
// unique-overload inference does not prematurely commit the result to the declared (nullable) nullness. The real
// nullness is resolved post argument type-checking (see below), once the argument types are known.
let minfo =
if not minfo.IsConstructor && g.checkNullness && g.langVersion.SupportsFeature LanguageFeature.NotNullIfNotNull then
match NotNullIfNotNullParamNames g minfo with
| [ _ ] ->
let retTy = minfo.GetFSharpReturnType(cenv.amap, mMethExpr, callerTyArgs)
MethInfoWithModifiedReturnType(minfo, replaceNullnessOfTy (NewNullnessVar()) retTy)
| _ -> minfo
else
minfo

CalledMeth<SynExpr>(cenv.infoReader, Some(env.NameEnv), isCheckingAttributeCall, FreshenMethInfo, mMethExpr, ad, minfo, minst, callerTyArgs, pinfoOpt, callerObjArgTys, callerArgs, usesParamArrayConversion, true, objTyOpt, staticTyOpt)

let preArgumentTypeCheckingCalledMethGroup =
Expand Down Expand Up @@ -10505,6 +10559,27 @@ and TcMethodApplication
match tyArgsOpt with
| Some tyargs -> minfo.AdjustUserTypeInstForFSharpStyleIndexedExtensionMembers tyargs
| None -> minst

let minfo =
if not minfo.IsConstructor && g.checkNullness && g.langVersion.SupportsFeature LanguageFeature.NotNullIfNotNull then
// 'minfo' may already carry a placeholder return nullness from unique-overload inference (phase 1);
// strip it back to the base method before applying the real (argument-derived) nullness.
let baseMinfo = match minfo with MethInfoWithModifiedReturnType(inner, _) -> inner | _ -> minfo
match NotNullIfNotNullParamNames g baseMinfo with
| [ paramName ] ->
match TryGetCallerArgType g baseMinfo callerArgs paramName with
| Some callerArgTy ->
let retTy = baseMinfo.GetFSharpReturnType(cenv.amap, mMethExpr, callerTyArgs)
let argNullness =
match GetTyparTyIfSupportsNull g callerArgTy with
| ValueSome _ -> g.knownWithNull
| ValueNone -> nullnessOfTy g callerArgTy
MethInfoWithModifiedReturnType(baseMinfo, replaceNullnessOfTy argNullness retTy)
| None -> baseMinfo
| _ -> baseMinfo
else
minfo

CalledMeth<Expr>(cenv.infoReader, Some(env.NameEnv), isCheckingAttributeCall, FreshenMethInfo, mMethExpr, ad, minfo, minst, callerTyArgs, pinfoOpt, callerObjArgTys, callerArgs, usesParamArrayConversion, true, objTyOpt, staticTyOpt))

// Commit unassociated constraints prior to member overload resolution where there is ambiguity
Expand Down
8 changes: 8 additions & 0 deletions src/Compiler/Checking/MethodCalls.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1250,6 +1250,14 @@ let rec BuildMethodCall tcVal g amap isMutable m isProp minfo valUseFlags minst
let expr = mkCoerceExpr (expr, retTy, m, exprTy)
expr, retTy

| MethInfoWithModifiedReturnType((FSMeth(_, _, vref, _) as innerMeth), retTy) ->
// Build the inner call directly, without re-invoking TakeObjAddrForMethodCall.
let vExpr, vExprTy = tcVal vref valUseFlags (innerMeth.DeclaringTypeInst @ minst) m
let expr, exprTy = BuildFSharpMethodApp g m vref vExpr vExprTy allArgs

let expr = mkCoerceExpr (expr, retTy, m, exprTy)
expr, retTy

| MethInfoWithModifiedReturnType _ ->
failwith "MethInfoWithModifiedReturnType: unexpected inner method kind"

Expand Down
2 changes: 1 addition & 1 deletion src/Compiler/Checking/NicePrint.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1723,7 +1723,7 @@ module InfoMemberPrinting =

let layout,paramLayouts =
match denv.showCsharpCodeAnalysisAttributes, minfo with
| true, ILMeth(_g,mi,_e) ->
| true, (ILMeth(_, mi, _) | MethInfoWithModifiedReturnType(ILMeth(_, mi, _), _)) ->
let methodLayout =
// Render Method attributes and [return:..] attributes on separate lines above (@@) the method definition
PrintTypes.layoutCsharpCodeAnalysisIlAttributes denv (minfo.GetCustomAttrs()) (squareAngleL >> (@@)) layout
Expand Down
4 changes: 2 additions & 2 deletions src/Compiler/DependencyManager/AssemblyResolveHandler.fs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ type AssemblyResolveHandlerCoreclr(assemblyProbingPaths: AssemblyResolutionProbe

let assemblyPathOpt =
assemblyPaths
|> Seq.tryFind (fun path -> Path.GetFileNameWithoutExtension(path) = simpleName)
|> Seq.tryFind (fun path -> String.Equals(Path.GetFileNameWithoutExtension(path), simpleName))

match assemblyPathOpt with
| Some path -> loadAssembly path
Expand Down Expand Up @@ -84,7 +84,7 @@ type AssemblyResolveHandlerDeskTop(assemblyProbingPaths: AssemblyResolutionProbe

let assemblyPathOpt =
assemblyPaths
|> Seq.tryFind (fun path -> Path.GetFileNameWithoutExtension(path) = simpleName)
|> Seq.tryFind (fun path -> String.Equals(Path.GetFileNameWithoutExtension(path), simpleName))

match assemblyPathOpt with
| Some path -> Assembly.LoadFrom path
Expand Down
1 change: 1 addition & 0 deletions src/Compiler/FSComp.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1821,3 +1821,4 @@ featurePreprocessorElif,"#elif preprocessor directive"
3888,implAttributeMissingFromSignature,"The attribute '%s' is present on '%s' in the implementation but not in the signature, which takes precedence for tooling and consumers. Add the attribute to the signature, to ensure the attribute is not ignored by the compiler."
featureExceptionFieldSerializationSupport,"emit GetObjectData and field-restoring deserialization constructor for exception types"
featureErrorOnMissingSignatureAttribute,"error (rather than warning) when an enforced compiler-semantic attribute is present in the .fs but missing from the .fsi"
featureNotNullIfNotNull,"honor the 'NotNullIfNotNull' attribute on a method's return value"
3 changes: 3 additions & 0 deletions src/Compiler/Facilities/LanguageFeatures.fs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ type LanguageFeature =
| PreprocessorElif
| ExceptionFieldSerializationSupport
| ErrorOnMissingSignatureAttribute
| NotNullIfNotNull

/// LanguageVersion management
type LanguageVersion(versionText, ?disabledFeaturesArray: LanguageFeature array) =
Expand Down Expand Up @@ -254,6 +255,7 @@ type LanguageVersion(versionText, ?disabledFeaturesArray: LanguageFeature array)
LanguageFeature.WarnWhenFunctionValueUsedAsInterpolatedStringArg, languageVersion110
LanguageFeature.PreprocessorElif, languageVersion110
LanguageFeature.ExceptionFieldSerializationSupport, languageVersion110
LanguageFeature.NotNullIfNotNull, languageVersion110

// Difference between languageVersion110 and preview - 11.0 gets turned on automatically by picking a preview .NET 11 SDK
// previewVersion is only when "preview" is specified explicitly in project files and users also need a preview SDK
Expand Down Expand Up @@ -459,6 +461,7 @@ type LanguageVersion(versionText, ?disabledFeaturesArray: LanguageFeature array)
| LanguageFeature.PreprocessorElif -> FSComp.SR.featurePreprocessorElif ()
| LanguageFeature.ExceptionFieldSerializationSupport -> FSComp.SR.featureExceptionFieldSerializationSupport ()
| LanguageFeature.ErrorOnMissingSignatureAttribute -> FSComp.SR.featureErrorOnMissingSignatureAttribute ()
| LanguageFeature.NotNullIfNotNull -> FSComp.SR.featureNotNullIfNotNull ()

/// Get a version string associated with the given feature.
static member GetFeatureVersionString feature =
Expand Down
1 change: 1 addition & 0 deletions src/Compiler/Facilities/LanguageFeatures.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ type LanguageFeature =
| PreprocessorElif
| ExceptionFieldSerializationSupport
| ErrorOnMissingSignatureAttribute
| NotNullIfNotNull

/// LanguageVersion management
type LanguageVersion =
Expand Down
6 changes: 6 additions & 0 deletions src/Compiler/TypedTree/TypedTreeOps.Attributes.fs
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ module internal ILExtensions =
WellKnownILAttributes.SetsRequiredMembersAttribute
| "System.ObsoleteAttribute" -> WellKnownILAttributes.ObsoleteAttribute
| "System.Diagnostics.CodeAnalysis.ExperimentalAttribute" -> WellKnownILAttributes.ExperimentalAttribute
| "System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute" -> WellKnownILAttributes.NotNullIfNotNullAttribute
| "System.AttributeUsageAttribute" -> WellKnownILAttributes.AttributeUsageAttribute
| _ -> WellKnownILAttributes.None

Expand Down Expand Up @@ -592,6 +593,11 @@ module internal AttributeHelpers =
| "ConditionalAttribute" -> WellKnownValAttributes.ConditionalAttribute
| _ -> WellKnownValAttributes.None

| [| "System"; "Diagnostics"; "CodeAnalysis"; name |] ->
match name with
| "NotNullIfNotNullAttribute" -> WellKnownValAttributes.NotNullIfNotNullAttribute
| _ -> WellKnownValAttributes.None

| [| "System"; name |] ->
match name with
| "ThreadStaticAttribute" -> WellKnownValAttributes.ThreadStaticAttribute
Expand Down
1 change: 1 addition & 0 deletions src/Compiler/TypedTree/WellKnownAttribs.fs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ type internal WellKnownValAttributes =
| NoEagerConstraintApplicationAttribute = (1uL <<< 38)
| ValueAsStaticPropertyAttribute = (1uL <<< 39)
| TailCallAttribute = (1uL <<< 40)
| NotNullIfNotNullAttribute = (1uL <<< 41)
| NotComputed = (1uL <<< 63)

module internal Flags =
Expand Down
1 change: 1 addition & 0 deletions src/Compiler/TypedTree/WellKnownAttribs.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ type internal WellKnownValAttributes =
| NoEagerConstraintApplicationAttribute = (1uL <<< 38)
| ValueAsStaticPropertyAttribute = (1uL <<< 39)
| TailCallAttribute = (1uL <<< 40)
| NotNullIfNotNullAttribute = (1uL <<< 41)
| NotComputed = (1uL <<< 63)

module internal Flags =
Expand Down
2 changes: 1 addition & 1 deletion src/Compiler/Utilities/range.fs
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ type Range(code1: int64, code2: int64) =
member m.FileName = fileOfFileIndex m.FileIndex

member internal m.ShortFileName =
Path.GetFileName(fileOfFileIndex m.FileIndex) |> nonNull
Path.GetFileName(fileOfFileIndex m.FileIndex) |> Unchecked.nonNull

member m.ApplyLineDirectives() =
match LineDirectives.store.TryFind m.FileIndex with
Expand Down
5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.cs.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.de.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.es.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.fr.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.it.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.ja.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.ko.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading