C
C#15mo ago
Kiel

❔ Weird Record compilation error: "must be a readable instance property or field"

I'd post it in the title, but it's too long 😭
Record member 'LfsResponseObject OpenFastDL.Api.LFS.LfsResponseObject.Error(LfsObjectError)' must be a readable instance property or field of type 'LfsObjectError' to match positional parameter 'Error'
Record member 'LfsResponseObject OpenFastDL.Api.LFS.LfsResponseObject.Error(LfsObjectError)' must be a readable instance property or field of type 'LfsObjectError' to match positional parameter 'Error'
Here's the records responsible for this error:
public abstract record LfsResponseObject(
[property: JsonPropertyName("oid")]
string Hash,
[property: JsonPropertyName("size")]
long Size);

public record LfsResponseErrorObject(
string Hash,
long Size,
[property: JsonPropertyName("error")]
LfsObjectError Error) : LfsResponseObject(Hash, Size); // [CS8866] Record member 'OpenFastDL.Api.LFS.LfsResponseObject.Error' must be a readable instance property or field of type 'OpenFastDL.Api.LFS.LfsObjectError' to match positional parameter 'Error'.

public record LfsObjectError(
[property: JsonPropertyName("code")]
HttpStatusCode Code,
[property: JsonPropertyName("message")]
string Message);
public abstract record LfsResponseObject(
[property: JsonPropertyName("oid")]
string Hash,
[property: JsonPropertyName("size")]
long Size);

public record LfsResponseErrorObject(
string Hash,
long Size,
[property: JsonPropertyName("error")]
LfsObjectError Error) : LfsResponseObject(Hash, Size); // [CS8866] Record member 'OpenFastDL.Api.LFS.LfsResponseObject.Error' must be a readable instance property or field of type 'OpenFastDL.Api.LFS.LfsObjectError' to match positional parameter 'Error'.

public record LfsObjectError(
[property: JsonPropertyName("code")]
HttpStatusCode Code,
[property: JsonPropertyName("message")]
string Message);
What's going on here? I have several other record types defined that have no issue having other records as positional parameters/properties. Example:
public record LfsBatchTransferRequest(
[property: JsonPropertyName("operation"), JsonConverter(typeof(JsonStringEnumConverter))]
LfsOperation Operation,
[property: JsonPropertyName("objects")]
ICollection<LfsRequestObject> Objects, // collection of record type
ICollection<LfsTransfer>? Transfers = null, // collection of record type
[property: JsonPropertyName("ref")]
LfsRequestObjectRef? Ref = null, // record type
string? HashAlgorithm = null);
public record LfsBatchTransferRequest(
[property: JsonPropertyName("operation"), JsonConverter(typeof(JsonStringEnumConverter))]
LfsOperation Operation,
[property: JsonPropertyName("objects")]
ICollection<LfsRequestObject> Objects, // collection of record type
ICollection<LfsTransfer>? Transfers = null, // collection of record type
[property: JsonPropertyName("ref")]
LfsRequestObjectRef? Ref = null, // record type
string? HashAlgorithm = null);
17 Replies
JakenVeina
JakenVeina15mo ago
I'm gonna guess that inheritance isn't supported with constructor-definition syntax
JakenVeina
JakenVeina15mo ago
I'm not seeing any examples of inheritance used together with constructor-definition syntax
Kiel
Kiel15mo ago
Here's the most confusing part: Renaming the property makes the error go away.
Kiel
Kiel15mo ago
Kiel
Kiel15mo ago
Kiel
Kiel15mo ago
thisisfine confirmed it's not rider/R# complaining about a false error, it actually fails to build in the former and succeeds in the latter
Kiel
Kiel15mo ago
I actually thought you were on to something for a moment. I'm actually writing my own wrapper for the git-lfs API, and I was previously using Estranged.Lfs which was an existing lib that partially wrapped it. It does not have an LfsResponseObject type at all, and when I hover over it in Rider, it's showing me my own type and base type i'm deriving it from But I think I just realized what it is I defined some convenience methods on the LfsResponseObject type, one of which is a static method called, you guessed it, Error
public abstract record LfsResponseObject(
[property: JsonPropertyName("oid")]
string Hash,
[property: JsonPropertyName("size")]
long Size)
{
public abstract bool HasError { get; }
// note: all these helper methods neglect to set Hash/Size because DefaultLfsObjectManager sets them
public static LfsResponseObject Error(LfsObjectError error)
=> new LfsResponseErrorObject(default!, default, error);

public static LfsResponseObject Error(string message, HttpStatusCode statusCode = HttpStatusCode.BadRequest)
=> new LfsResponseErrorObject(default!, default, new LfsObjectError(statusCode, message));

public static LfsResponseObject BasicDownload(Uri downloadUri, long size,
IReadOnlyDictionary<string, string>? headers = null, int? expiryInSeconds = null, DateTimeOffset? expiresA
{
return new LfsResponseDataObject(default!, default, new Dictionary<string, LfsResponseObjectAction>
{
[LfsResponseObjectAction.DOWNLOAD_ACTION_NAME] = new(downloadUri, headers, expiryInSeconds, expiresAt)
});
}

public static LfsResponseObject BasicUpload(Uri uploadUri,
IReadOnlyDictionary<string, string>? headers = null, int? expiryInSeconds = null, DateTimeOffset? expiresA
{
return new LfsResponseDataObject(default!, default, new Dictionary<string, LfsResponseObjectAction>
{
[LfsResponseObjectAction.UPLOAD_ACTION_NAME] = new(uploadUri, headers, expiryInSeconds, expiresAt)
});
}
public static implicit operator LfsResponseObject(LfsObjectError error)
=> Error(error);
}
public abstract record LfsResponseObject(
[property: JsonPropertyName("oid")]
string Hash,
[property: JsonPropertyName("size")]
long Size)
{
public abstract bool HasError { get; }
// note: all these helper methods neglect to set Hash/Size because DefaultLfsObjectManager sets them
public static LfsResponseObject Error(LfsObjectError error)
=> new LfsResponseErrorObject(default!, default, error);

public static LfsResponseObject Error(string message, HttpStatusCode statusCode = HttpStatusCode.BadRequest)
=> new LfsResponseErrorObject(default!, default, new LfsObjectError(statusCode, message));

public static LfsResponseObject BasicDownload(Uri downloadUri, long size,
IReadOnlyDictionary<string, string>? headers = null, int? expiryInSeconds = null, DateTimeOffset? expiresA
{
return new LfsResponseDataObject(default!, default, new Dictionary<string, LfsResponseObjectAction>
{
[LfsResponseObjectAction.DOWNLOAD_ACTION_NAME] = new(downloadUri, headers, expiryInSeconds, expiresAt)
});
}

public static LfsResponseObject BasicUpload(Uri uploadUri,
IReadOnlyDictionary<string, string>? headers = null, int? expiryInSeconds = null, DateTimeOffset? expiresA
{
return new LfsResponseDataObject(default!, default, new Dictionary<string, LfsResponseObjectAction>
{
[LfsResponseObjectAction.UPLOAD_ACTION_NAME] = new(uploadUri, headers, expiryInSeconds, expiresAt)
});
}
public static implicit operator LfsResponseObject(LfsObjectError error)
=> Error(error);
}
did not know that was gonna be an issue
sanic
sanic15mo ago
I feel like if we had used different symbol display options in the diagnostic, this would have been pretty easy to identify. Does the error go away if you explicitly shadow this method (e.g. explicitly add an Error property to the type where the error is being reported.)
Kiel
Kiel15mo ago
yep
Kiel
Kiel15mo ago
It's not an issue really, I'll just rename the helper methods to FromError instead of Error blobthumbsup glad to know what the cause was, finally. The whole "renaming fixes it" took awhile to click as to the issue
sanic
sanic15mo ago
It would be handy if you could file a bug on us, asking us to use a symbol display format which makes it more clear what the offending member is. e.g. if it was
Record member 'LfsResponseObject.Error(string, HttpStatusCode)' must be a readable instance property or field
I think it would be clear
Kiel
Kiel15mo ago
Would an appropriate issue be a suggestion for a better error message for CS8866 specifically?
sanic
sanic15mo ago
Yeah, just describe your scenario, if someone tries to fix it we can consider if any other related diagnostics should also be changed. I wouldn't include the diagnostic ID in the issue title, just paraphrase the general diagnostic message, e.g. "'Record member does not match positional parameter' diagnostic message is unclear" or something like that.
Kiel
Kiel15mo ago
I submitted a simplified version since the repro I provided seemed valid enough of a real use case (and my records in my actual project are a mess at the moment 😅 https://github.com/dotnet/roslyn/issues/67869 But thanks for the help!
Accord
Accord15mo ago
Was this issue resolved? If so, run /close - otherwise I will mark this as stale and this post will be archived until there is new activity.