Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

WIP: Traits for IL fields #16481

Draft
wants to merge 11 commits into
base: main
Choose a base branch
from
4 changes: 2 additions & 2 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
{
"id": "fsiArgsPrompt",
"description": "Enter arguments for fsi (optional)",
"default": "",
"default": "script.fsx",
vzarytovskii marked this conversation as resolved.
Show resolved Hide resolved
"type": "promptString",
}
],
Expand All @@ -25,7 +25,7 @@
// If you have changed target frameworks, make sure to update the program p
"program": "${workspaceFolder}/artifacts/bin/fsi/Debug/net8.0/fsi.dll",
"args": [
"${input:fsiArgsPrompt}"
"${input:fsiArgsPrompt}",
],
"cwd": "${workspaceFolder}",
"console": "integratedTerminal", // This is the default to be able to run in Codespaces.
Expand Down
64 changes: 52 additions & 12 deletions src/Compiler/Checking/ConstraintSolver.fs
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,7 @@ type TraitConstraintSolution =
| TTraitSolved of minfo: MethInfo * minst: TypeInst * staticTyOpt: TType option
| TTraitSolvedRecdProp of fieldInfo: RecdFieldInfo * isSetProp: bool
| TTraitSolvedAnonRecdProp of anonRecdTypeInfo: AnonRecdTypeInfo * typeInst: TypeInst * index: int
| TTraitSolvedField of fieldInfo: ILFieldInfo * isSetField: bool

let BakedInTraitConstraintNames =
[ "op_Division" ; "op_Multiply"; "op_Addition"
Expand Down Expand Up @@ -1671,7 +1672,7 @@ and SolveMemberConstraint (csenv: ConstraintSolverEnv) ignoreUnresolvedOverload

| [], _, false, "Pow", [argTy1; argTy2]
when isFpTy g argTy1 ->

do! SolveDimensionlessNumericType csenv ndeep m2 trace argTy1
do! SolveTypeEqualsTypeKeepAbbrevs csenv ndeep m2 trace argTy2 argTy1
do! SolveTypeEqualsTypeKeepAbbrevs csenv ndeep m2 trace retTy argTy1
Expand Down Expand Up @@ -1727,9 +1728,40 @@ and SolveMemberConstraint (csenv: ConstraintSolverEnv) ignoreUnresolvedOverload
else
None

// TODO(vlza): this probably can be optimized and simplified, to unify search for record fields and regular fields (?)
let fieldSearch =
// Name (nm) will always start with "get_" or "set_", since it is how it's named in the trait info.
// If we want to also support getting and setting for fields, we should add a "trimmed" name + whether it's get or set via flag.
// This here is just a hacky way of "transforming" the property into field lookup as well.
// However we do the same for the (anon)recd fields already (sort of)
let isGet = nm.StartsWithOrdinal("get_")
let isSet = nm.StartsWithOrdinal("set_")
if not isRigid && ((argTys.IsEmpty && isGet) || isSet) then
let nm = nm[4..]

let fields =
[| for ty in supportTys do
let item = TryFindIntrinsicNamedItemOfType csenv.InfoReader (nm, AccessibleFromEverywhere, false) FindMemberFlag.IgnoreOverrides m ty
match item with
| Some (ILFieldItem [ ilfinfo ]) when
ilfinfo.IsStatic = (not memFlags.IsInstance)
&& ilfinfo.IsInitOnly = (not isSet)
&& IsILFieldInfoAccessible g amap m AccessibleFromEverywhere ilfinfo
&& ilfinfo.LiteralValue.IsSome
&& not ilfinfo.IsSpecialName ->
yield ilfinfo
| _ -> ()
|]
if fields.Length = 1 then
Some (fields[0], isSet)
else
None
else
None

// Now check if there are no feasible solutions at all
match minfos, recdPropSearch, anonRecdPropSearch with
| [], None, None when MemberConstraintIsReadyForStrongResolution csenv traitInfo ->
match minfos, recdPropSearch, anonRecdPropSearch, fieldSearch with
| [], None, None, None when MemberConstraintIsReadyForStrongResolution csenv traitInfo ->
if supportTys |> List.exists (isFunTy g) then
return! ErrorD (ConstraintSolverError(FSComp.SR.csExpectTypeWithOperatorButGivenFunction(ConvertValLogicalNameToDisplayNameCore nm), m, m2))
elif supportTys |> List.exists (isAnyTupleTy g) then
Expand Down Expand Up @@ -1786,20 +1818,20 @@ and SolveMemberConstraint (csenv: ConstraintSolverEnv) ignoreUnresolvedOverload
(fun (a, _) -> Option.isSome a)
(fun trace -> ResolveOverloading csenv (WithTrace trace) nm ndeep (Some traitInfo) CallerArgs.Empty AccessibleFromEverywhere calledMethGroup false (Some (MustEqual retTy)))

match anonRecdPropSearch, recdPropSearch, methOverloadResult with
| Some (anonInfo, tinst, i), None, None ->
match anonRecdPropSearch, recdPropSearch, methOverloadResult, fieldSearch with
| Some (anonInfo, tinst, i), None, None, None ->
// OK, the constraint is solved by a record property. Assert that the return types match.
let rty2 = List.item i tinst
do! SolveTypeEqualsTypeKeepAbbrevs csenv ndeep m2 trace retTy rty2
return TTraitSolvedAnonRecdProp(anonInfo, tinst, i)

| None, Some (rfinfo, isSetProp), None ->
| None, Some (rfinfo, isSetProp), None, None ->
// OK, the constraint is solved by a record property. Assert that the return types match.
let rty2 = if isSetProp then g.unit_ty else rfinfo.FieldType
do! SolveTypeEqualsTypeKeepAbbrevs csenv ndeep m2 trace retTy rty2
return TTraitSolvedRecdProp(rfinfo, isSetProp)

| None, None, Some (calledMeth: CalledMeth<_>) ->
| None, None, Some (calledMeth: CalledMeth<_>), None->
// OK, the constraint is solved.
let minfo = calledMeth.Method

Expand All @@ -1814,7 +1846,11 @@ and SolveMemberConstraint (csenv: ConstraintSolverEnv) ignoreUnresolvedOverload
else
do! CheckMethInfoAttributes g m None minfo
return TTraitSolved (minfo, calledMeth.CalledTyArgs, calledMeth.OptionalStaticType)

| None, None, None, Some (ilfinfo, isSet) ->
// OK, the constraint is solved by a field. Assert that types match.
let ty2 = ilfinfo.FieldType(amap, m)
do! SolveTypeEqualsTypeKeepAbbrevs csenv ndeep m2 trace retTy ty2
return TTraitSolvedField(ilfinfo, isSet)
| _ ->
do! AddUnsolvedMemberConstraint csenv ndeep m2 trace permitWeakResolution ignoreUnresolvedOverload traitInfo errors
return TTraitUnsolved
Expand Down Expand Up @@ -1862,27 +1898,31 @@ and AddUnsolvedMemberConstraint csenv ndeep m2 trace permitWeakResolution ignore
/// each member constraint.
and RecordMemberConstraintSolution css m trace traitInfo traitConstraintSln =
match traitConstraintSln with
| TTraitUnsolved ->
| TTraitUnsolved ->
ResultD false

| TTraitSolved (minfo, minst, staticTyOpt) ->
let sln = MemberConstraintSolutionOfMethInfo css m minfo minst staticTyOpt
TransactMemberConstraintSolution traitInfo trace sln
ResultD true

| TTraitBuiltIn ->
| TTraitBuiltIn ->
TransactMemberConstraintSolution traitInfo trace BuiltInSln
ResultD true

| TTraitSolvedRecdProp (rfinfo, isSet) ->
| TTraitSolvedRecdProp (rfinfo, isSet) ->
let sln = FSRecdFieldSln(rfinfo.TypeInst,rfinfo.RecdFieldRef,isSet)
TransactMemberConstraintSolution traitInfo trace sln
ResultD true

| TTraitSolvedAnonRecdProp (anonInfo, tinst, i) ->
| TTraitSolvedAnonRecdProp (anonInfo, tinst, i) ->
let sln = FSAnonRecdFieldSln(anonInfo, tinst, i)
TransactMemberConstraintSolution traitInfo trace sln
ResultD true
| TTraitSolvedField (ilfinfo, isSet) ->
let sln = ILFieldSln(ilfinfo.TypeInst, ilfinfo.ILFieldRef, isSet)
TransactMemberConstraintSolution traitInfo trace sln
ResultD true

/// Convert a MethInfo into the data we save in the TAST
and MemberConstraintSolutionOfMethInfo css m minfo minst staticTyOpt =
Expand Down
10 changes: 9 additions & 1 deletion src/Compiler/TypedTree/TypedTree.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2494,7 +2494,7 @@ type TraitConstraintInfo =
member x.DebugText = x.ToString()

override x.ToString() = "TTrait(" + x.MemberLogicalName + ")"

/// Represents the solution of a member constraint during inference.
[<NoEquality; NoComparison (* ; StructuredFormatDisplay("{DebugText}") *) >]
type TraitConstraintSln =
Expand Down Expand Up @@ -2529,6 +2529,14 @@ type TraitConstraintSln =
/// staticTyOpt -- the static type governing a static virtual call, if any
| ILMethSln of ty: TType * extOpt: ILTypeRef option * ilMethodRef: ILMethodRef * minst: TypeInst * staticTyOpt: TType option

/// ILFieldSln(tinst, ilfref, isSet)
///
/// Indicates a trait is solved by a .NET field.
/// tinst -- the instantiation of the declaring type
/// ilfref -- the reference to the field
/// isSet -- indicates if this is a set of a field
| ILFieldSln of tinst: TypeInst * ilfref: ILFieldRef * isSet: bool

/// ClosedExprSln expr
///
/// Indicates a trait is solved by an erased provided expression
Expand Down
8 changes: 8 additions & 0 deletions src/Compiler/TypedTree/TypedTree.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -1764,6 +1764,14 @@ type TraitConstraintSln =
minst: TypeInst *
staticTyOpt: TType option

/// ILFieldSln(tinst, ilfref, isSet)
///
/// Indicates a trait is solved by a .NET field.
/// tinst -- the instantiation of the declaring type
/// ilfref -- the reference to the field
/// isSet -- indicates if this is a set of a field
| ILFieldSln of tinst: TypeInst * ilfref: ILFieldRef * isSet: bool

/// ClosedExprSln expr
///
/// Indicates a trait is solved by an erased provided expression
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,51 @@ open Xunit
open FSharp.Test.Compiler

module MemberConstraints =
open FSharp.Test

[<Fact>]
let ``Member constraint for fields in C# library`` () =
let csLib =
CSharp """
namespace CsLib
{
public class Class1
{
public string Id;
public Class1(string id) {
Id = id;
}
}

public class Class2
{
public string Id { get; set; }
public Class2(string id) {
Id = id;
}
}
}"""
|> withCSharpLanguageVersion CSharpLanguageVersion.Preview
|> withName "csLib"

let app =
FSharp """
module Lib

let inline f<'T when 'T: (member Id: string)> (x: 'T) = x.Id

[<EntryPoint>]
let main _ =
f (CsLib.Class1("Class1")) |> printfn "%s"
f (CsLib.Class2("Class2")) |> printfn "%s"
0
""" |> withReferences [csLib]

app
|> asExe
|> compile
|> run
|> verifyOutput "aaaaa"

[<Fact>]
let ``Invalid member constraint with ErrorRanges``() = // Regression test for FSharp1.0:2262
Expand Down
Loading