Skip to content

Commit

Permalink
Merge branch 'main' into update_pinned_api_parser
Browse files Browse the repository at this point in the history
  • Loading branch information
praveenkuttappan authored Oct 16, 2024
2 parents f4d31f8 + 9d769f7 commit 0287594
Show file tree
Hide file tree
Showing 41 changed files with 1,125 additions and 281 deletions.
8 changes: 8 additions & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,11 @@
######################
/eng/emitters/ @m-nash

######################
# IDE
######################
/packages/typespec-vs/ @RodgeFu @bterlson @markcowl @allenjzhang @timotheeguerin
/packages/typespec-vscode/ @RodgeFu @bterlson @markcowl @allenjzhang @timotheeguerin
/packages/compiler/src/server/ @RodgeFu @bterlson @markcowl @allenjzhang @timotheeguerin


Original file line number Diff line number Diff line change
Expand Up @@ -733,24 +733,29 @@ private List<MethodBodyStatement> BuildDeserializePropertiesStatements(ScopedApi
for (int i = 0; i < parameters.Count; i++)
{
var parameter = parameters[i];
if (parameter.Property is { } property)
if (parameter.Property != null || parameter.Field != null)
{
// handle additional properties
if (property != _additionalBinaryDataProperty && property.IsAdditionalProperties)
if (parameter.Property != null && parameter.Property != _additionalBinaryDataProperty && parameter.Property.IsAdditionalProperties)
{
AddAdditionalPropertiesValueKindStatements(additionalPropsValueKindBodyStatements, property, jsonProperty);
AddAdditionalPropertiesValueKindStatements(additionalPropsValueKindBodyStatements, parameter.Property, jsonProperty);
continue;
}

var wireInfo = parameter.Property?.WireInfo ?? parameter.Field?.WireInfo;

// By default, we should only deserialize properties with wire info. Those properties without wire info indicate they are not spec properties.
if (property.WireInfo is not { } wireInfo)
if (wireInfo == null)
{
continue;
}
var propertySerializationName = wireInfo.SerializedName;
var propertyName = parameter.Property?.Name ?? parameter.Field?.Name;
var propertyType = parameter.Property?.Type ?? parameter.Field?.Type;
var propertyExpression = parameter.Property?.AsVariableExpression ?? parameter.Field?.AsVariableExpression;
var checkIfJsonPropEqualsName = new IfStatement(jsonProperty.NameEquals(propertySerializationName))
{
DeserializeProperty(property, jsonProperty, serializationAttributes)
DeserializeProperty(propertyName!, propertyType!, wireInfo, propertyExpression!, jsonProperty, serializationAttributes)
};
propertyDeserializationStatements.Add(checkIfJsonPropEqualsName);
}
Expand Down Expand Up @@ -1051,15 +1056,18 @@ private static SwitchStatement CreateDeserializeAdditionalPropsValueKindCheck(
}

private MethodBodyStatement[] DeserializeProperty(
PropertyProvider property,
string propertyName,
CSharpType propertyType,
PropertyWireInformation wireInfo,
VariableExpression variableExpression,
ScopedApi<JsonProperty> jsonProperty,
IEnumerable<AttributeData> serializationAttributes)
{
var serializationFormat = property.WireInfo?.SerializationFormat ?? SerializationFormat.Default;
var propertyVarReference = property.AsVariableExpression;
var serializationFormat = wireInfo.SerializationFormat;
var propertyVarReference = variableExpression;
var deserializationStatements = new MethodBodyStatement[2]
{
DeserializeValue(property.Type, jsonProperty.Value(), serializationFormat, out ValueExpression value),
DeserializeValue(propertyType, jsonProperty.Value(), serializationFormat, out ValueExpression value),
propertyVarReference.Assign(value).Terminate()
};

Expand All @@ -1071,7 +1079,7 @@ private MethodBodyStatement[] DeserializeProperty(
out _,
out _,
out var deserializationHook,
out _) && name == property.Name && deserializationHook != null)
out _) && name == propertyName && deserializationHook != null)
{
deserializationStatements =
[Static().Invoke(
Expand All @@ -1084,7 +1092,7 @@ private MethodBodyStatement[] DeserializeProperty(

return
[
DeserializationPropertyNullCheckStatement(property, jsonProperty, propertyVarReference),
DeserializationPropertyNullCheckStatement(propertyType, wireInfo, jsonProperty, propertyVarReference),
deserializationStatements,
Continue
];
Expand All @@ -1097,14 +1105,15 @@ private MethodBodyStatement[] DeserializeProperty(
/// return a null check for the json property.
/// </summary>
private static MethodBodyStatement DeserializationPropertyNullCheckStatement(
PropertyProvider property,
CSharpType propertyType,
PropertyWireInformation wireInfo,
ScopedApi<JsonProperty> jsonProperty,
VariableExpression propertyVarRef)
{
// Produces: if (prop.Value.ValueKind == System.Text.Json.JsonValueKind.Null)
var checkEmptyProperty = jsonProperty.Value().ValueKindEqualsNull();
CSharpType serializedType = property.Type;
var propertyIsRequired = property.WireInfo?.IsRequired ?? false;
CSharpType serializedType = propertyType;
var propertyIsRequired = wireInfo.IsRequired;

if (serializedType.IsNullable || !propertyIsRequired)
{
Expand Down Expand Up @@ -1311,89 +1320,110 @@ private static MethodBodyStatement ThrowValidationFailException(ValueExpression
/// </summary>
private MethodBodyStatement[] CreateWritePropertiesStatements()
{
var properties = _model.CanonicalView.Properties;
List<MethodBodyStatement> propertyStatements = new();
foreach (var property in properties)
foreach (var property in _model.CanonicalView.Properties)
{
// we should only write those properties with a wire info. Those properties without wireinfo indicate they are not spec properties.
if (property.WireInfo is not { } wireInfo)
if (property.WireInfo == null)
{
continue;
}
var propertySerializationName = wireInfo.SerializedName;
var propertySerializationFormat = wireInfo.SerializationFormat;
var propertyIsReadOnly = wireInfo.IsReadOnly;
var propertyIsRequired = wireInfo.IsRequired;
var propertyIsNullable = wireInfo.IsNullable;

// Generate the serialization statements for the property
var serializationStatement = CreateSerializationStatement(property.Type, property, propertySerializationFormat);

// Check for custom serialization hooks
foreach (var attribute in _model.CustomCodeView?.GetAttributes()
.Where(a => a.AttributeClass?.Name == CodeGenAttributes.CodeGenSerializationAttributeName) ?? [])

propertyStatements.Add(CreateWritePropertyStatement(property.WireInfo, property.Type, property.Name, property));
}

foreach (var field in _model.CanonicalView.Fields)
{
// we should only write those properties with a wire info. Those properties without wireinfo indicate they are not spec properties.
if (field.WireInfo == null)
{
if (CodeGenAttributes.TryGetCodeGenSerializationAttributeValue(
attribute,
out var name,
out _,
out var serializationHook,
out _,
out _) && name == property.Name && serializationHook != null)
{
serializationStatement = This.Invoke(
serializationHook,
_utf8JsonWriterSnippet,
_serializationOptionsParameter)
.Terminate();
}
continue;
}

var writePropertySerializationStatements = new MethodBodyStatement[]
{
_utf8JsonWriterSnippet.WritePropertyName(propertySerializationName),
serializationStatement
};
propertyStatements.Add(CreateWritePropertyStatement(field.WireInfo, field.Type, field.Name, field));
}

// Wrap the serialization statement in a check for whether the property is defined
var wrapInIsDefinedStatement = WrapInIsDefined(property, property, propertyIsRequired, propertyIsReadOnly, propertyIsNullable, writePropertySerializationStatements);
if (propertyIsReadOnly && wrapInIsDefinedStatement is not IfStatement)
return [.. propertyStatements];
}

private MethodBodyStatement CreateWritePropertyStatement(
PropertyWireInformation wireInfo,
CSharpType propertyType,
string propertyName,
MemberExpression propertyExpression)
{
var propertySerializationName = wireInfo.SerializedName;
var propertySerializationFormat = wireInfo.SerializationFormat;
var propertyIsReadOnly = wireInfo.IsReadOnly;
var propertyIsRequired = wireInfo.IsRequired;
var propertyIsNullable = wireInfo.IsNullable;

// Generate the serialization statements for the property
var serializationStatement = CreateSerializationStatement(propertyType, propertyExpression, propertySerializationFormat);

// Check for custom serialization hooks
foreach (var attribute in _model.CustomCodeView?.GetAttributes()
.Where(a => a.AttributeClass?.Name == CodeGenAttributes.CodeGenSerializationAttributeName) ?? [])
{
if (CodeGenAttributes.TryGetCodeGenSerializationAttributeValue(
attribute,
out var name,
out _,
out var serializationHook,
out _,
out _) && name == propertyName && serializationHook != null)
{
wrapInIsDefinedStatement = new IfStatement(_isNotEqualToWireConditionSnippet)
{
wrapInIsDefinedStatement
};
serializationStatement = This.Invoke(
serializationHook,
_utf8JsonWriterSnippet,
_serializationOptionsParameter)
.Terminate();
}
propertyStatements.Add(wrapInIsDefinedStatement);
}

return [.. propertyStatements];
var writePropertySerializationStatements = new MethodBodyStatement[]
{
_utf8JsonWriterSnippet.WritePropertyName(propertySerializationName),
serializationStatement
};

// Wrap the serialization statement in a check for whether the property is defined
var wrapInIsDefinedStatement = WrapInIsDefined(
propertyExpression,
propertyType,
wireInfo,
propertyIsRequired,
propertyIsReadOnly,
propertyIsNullable,
writePropertySerializationStatements);
if (propertyIsReadOnly && wrapInIsDefinedStatement is not IfStatement)
{
wrapInIsDefinedStatement = new IfStatement(_isNotEqualToWireConditionSnippet)
{
wrapInIsDefinedStatement
};
}

return wrapInIsDefinedStatement;
}

/// <summary>
/// Wraps the serialization statement in a condition check to ensure only initialized and required properties are serialized.
/// </summary>
/// <param name="propertyProvider">The model property.</param>
/// <param name="propertyMemberExpression">The expression representing the property to serialize.</param>
/// <param name="writePropertySerializationStatement">The serialization statement to conditionally execute.</param>
/// <returns>A method body statement that includes condition checks before serialization.</returns>
private MethodBodyStatement WrapInIsDefined(
PropertyProvider propertyProvider,
MemberExpression propertyMemberExpression,
MemberExpression propertyExpression,
CSharpType propertyType,
PropertyWireInformation wireInfo,
bool propertyIsRequired,
bool propertyIsReadOnly,
bool propertyIsNullable,
MethodBodyStatement writePropertySerializationStatement)
{
var propertyType = propertyProvider.Type;

// Create the first conditional statement to check if the property is defined
if (propertyIsNullable)
{
writePropertySerializationStatement = CheckPropertyIsInitialized(
propertyProvider,
propertyType,
wireInfo,
propertyIsRequired,
propertyMemberExpression,
propertyExpression,
writePropertySerializationStatement);
}

Expand All @@ -1404,18 +1434,16 @@ private MethodBodyStatement WrapInIsDefined(
}

// Conditionally serialize based on whether the property is a collection or a single value
return CreateConditionalSerializationStatement(propertyType, propertyMemberExpression, propertyIsReadOnly, writePropertySerializationStatement);
return CreateConditionalSerializationStatement(propertyType, propertyExpression, propertyIsReadOnly, writePropertySerializationStatement);
}

private IfElseStatement CheckPropertyIsInitialized(
PropertyProvider propertyProvider,
CSharpType propertyType,
PropertyWireInformation wireInfo,
bool isPropRequired,
MemberExpression propertyMemberExpression,
MethodBodyStatement writePropertySerializationStatement)
{
var propertyType = propertyProvider.Type;
var propertySerialization = propertyProvider.WireInfo;
var propertyName = propertySerialization?.SerializedName ?? propertyProvider.Name;
ScopedApi<bool> propertyIsInitialized;

if (propertyType.IsCollection && !propertyType.IsReadOnlyMemory && isPropRequired)
Expand All @@ -1431,7 +1459,7 @@ private IfElseStatement CheckPropertyIsInitialized(
return new IfElseStatement(
propertyIsInitialized,
writePropertySerializationStatement,
_utf8JsonWriterSnippet.WriteNull(propertyName.ToVariableName()));
_utf8JsonWriterSnippet.WriteNull(wireInfo.SerializedName.ToVariableName()));
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,5 +84,26 @@ public async Task CanSerializeCustomPropertyFromBase()
Assert.IsNotNull(fullCtor);
Assert.IsTrue(fullCtor!.Signature.Parameters.Any(p => p.Name == "prop1"));
}

[Test]
public async Task CanCustomizePropertyUsingField()
{
var props = new[]
{
InputFactory.Property("Prop1", InputPrimitiveType.String),
};

var inputModel = InputFactory.Model("mockInputModel", properties: props, usage: InputModelTypeUsage.Json);
var plugin = await MockHelpers.LoadMockPluginAsync(
inputModels: () => [inputModel],
compilation: async () => await Helpers.GetCompilationFromDirectoryAsync());

var modelProvider = plugin.Object.OutputLibrary.TypeProviders.Single(t => t is ModelProvider);

Assert.AreEqual(0, modelProvider.Properties.Count);
var writer = new TypeProviderWriter(modelProvider);
var file = writer.Write();
Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content);
}
}
}
Loading

0 comments on commit 0287594

Please sign in to comment.