Skip to content
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
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
66 changes: 41 additions & 25 deletions src/Microsoft.ML.Data/Data/SchemaDefinition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,17 @@ public static SchemaDefinition Create(Type userType, Direction direction = Direc
if (memberInfo.GetCustomAttribute<NoColumnAttribute>() != null)
continue;

var customAttributes = memberInfo.GetCustomAttributes();
var customTypeAttributes = customAttributes.Where(x => x is DataViewTypeAttribute);
if (customTypeAttributes.Count() > 1)
throw Contracts.ExceptParam(nameof(userType), "Member {0} cannot be marked with multiple attributes, {1}, derived from {2}.",
memberInfo.Name, customTypeAttributes, typeof(DataViewTypeAttribute));
else if (customTypeAttributes.Count() == 1)
{
var customTypeAttribute = (DataViewTypeAttribute)customTypeAttributes.First();
customTypeAttribute.Register();
}

var mappingNameAttr = memberInfo.GetCustomAttribute<ColumnNameAttribute>();
string name = mappingNameAttr?.Name ?? memberInfo.Name;
// Disallow duplicate names, because the field enumeration order is not actually
Expand All @@ -392,37 +403,42 @@ public static SchemaDefinition Create(Type userType, Direction direction = Direc

InternalSchemaDefinition.GetVectorAndItemType(memberInfo, out bool isVector, out Type dataType);

PrimitiveDataViewType itemType;
var keyAttr = memberInfo.GetCustomAttribute<KeyTypeAttribute>();
if (keyAttr != null)
{
if (!KeyDataViewType.IsValidDataType(dataType))
throw Contracts.ExceptParam(nameof(userType), "Member {0} marked with KeyType attribute, but does not appear to be a valid kind of data for a key type", memberInfo.Name);
if (keyAttr.KeyCount == null)
itemType = new KeyDataViewType(dataType, dataType.ToMaxInt());
else
itemType = new KeyDataViewType(dataType, keyAttr.KeyCount.Count.GetValueOrDefault());
}
else
itemType = ColumnTypeExtensions.PrimitiveTypeFromType(dataType);

// Get the column type.
DataViewType columnType;
var vectorAttr = memberInfo.GetCustomAttribute<VectorTypeAttribute>();
if (vectorAttr != null && !isVector)
throw Contracts.ExceptParam(nameof(userType), $"Member {memberInfo.Name} marked with {nameof(VectorTypeAttribute)}, but does not appear to be a vector type", memberInfo.Name);
if (isVector)
if (!DataViewTypeManager.Knows(dataType, customAttributes))
{
int[] dims = vectorAttr?.Dims;
if (dims != null && dims.Any(d => d < 0))
throw Contracts.ExceptParam(nameof(userType), "Some of member {0}'s dimension lengths are negative");
if (Utils.Size(dims) == 0)
columnType = new VectorDataViewType(itemType, 0);
PrimitiveDataViewType itemType;
var keyAttr = memberInfo.GetCustomAttribute<KeyTypeAttribute>();
if (keyAttr != null)
{
if (!KeyDataViewType.IsValidDataType(dataType))
throw Contracts.ExceptParam(nameof(userType), "Member {0} marked with KeyType attribute, but does not appear to be a valid kind of data for a key type", memberInfo.Name);
if (keyAttr.KeyCount == null)
itemType = new KeyDataViewType(dataType, dataType.ToMaxInt());
else
itemType = new KeyDataViewType(dataType, keyAttr.KeyCount.Count.GetValueOrDefault());
}
else
itemType = ColumnTypeExtensions.PrimitiveTypeFromType(dataType);

var vectorAttr = memberInfo.GetCustomAttribute<VectorTypeAttribute>();
if (vectorAttr != null && !isVector)
throw Contracts.ExceptParam(nameof(userType), $"Member {memberInfo.Name} marked with {nameof(VectorTypeAttribute)}, but does not appear to be a vector type", memberInfo.Name);
if (isVector)
{
int[] dims = vectorAttr?.Dims;
if (dims != null && dims.Any(d => d < 0))
throw Contracts.ExceptParam(nameof(userType), "Some of member {0}'s dimension lengths are negative");
if (Utils.Size(dims) == 0)
columnType = new VectorDataViewType(itemType, 0);
else
columnType = new VectorDataViewType(itemType, dims);
}
else
columnType = new VectorDataViewType(itemType, dims);
columnType = itemType;
}
else
columnType = itemType;
columnType = DataViewTypeManager.GetDataViewType(dataType, customAttributes);

cols.Add(new Column(memberInfo.Name, columnType, name));
}
Expand Down
6 changes: 5 additions & 1 deletion src/Microsoft.ML.Data/DataView/DataViewConstructionUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,10 @@ private Delegate CreateGetter(DataViewType colType, InternalSchemaDefinition.Col
return Utils.MarshalInvoke(delForKey, keyRawType, peek, colType);
}
}
else if (DataViewTypeManager.Knows(colType))
{
del = CreateDirectGetterDelegate<int>;

@glebuk glebuk May 17, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

int [](start = 53, length = 3)

why int? #Resolved

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can be anything you want. The generic type will be over-write at the end of this function.


In reply to: 284945609 [](ancestors = 284945609)

}
else
{
// REVIEW: Is this even possible?
Expand Down Expand Up @@ -843,7 +847,7 @@ public AnnotationInfo(string kind, T value, DataViewType annotationType = null)
Contracts.Assert(value != null);
bool isVector;
Type itemType;
InternalSchemaDefinition.GetVectorAndItemType(typeof(T), "annotation value", out isVector, out itemType);
InternalSchemaDefinition.GetVectorAndItemType("annotation value", typeof(T), null, out isVector, out itemType);

if (annotationType == null)
{
Expand Down
18 changes: 11 additions & 7 deletions src/Microsoft.ML.Data/DataView/InternalSchemaDefinition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ public void AssertRep()
Contracts.Assert(Generator.GetMethodInfo().ReturnType == typeof(void));

// Checks that the return type of the generator is compatible with ColumnType.
GetVectorAndItemType(ComputedReturnType, "return type", out bool isVector, out Type itemType);
GetVectorAndItemType("return type", ComputedReturnType, null, out bool isVector, out Type itemType);
Contracts.Assert(isVector == ColumnType is VectorDataViewType);
Contracts.Assert(itemType == ColumnType.GetItemType().RawType);
}
Expand Down Expand Up @@ -147,11 +147,11 @@ public static void GetVectorAndItemType(MemberInfo memberInfo, out bool isVector
switch (memberInfo)
{
case FieldInfo fieldInfo:
GetVectorAndItemType(fieldInfo.FieldType, fieldInfo.Name, out isVector, out itemType);
GetVectorAndItemType(fieldInfo.Name, fieldInfo.FieldType, fieldInfo.GetCustomAttributes(), out isVector, out itemType);
break;

case PropertyInfo propertyInfo:
GetVectorAndItemType(propertyInfo.PropertyType, propertyInfo.Name, out isVector, out itemType);
GetVectorAndItemType(propertyInfo.Name, propertyInfo.PropertyType, propertyInfo.GetCustomAttributes(), out isVector, out itemType);
break;

default:
Expand All @@ -165,13 +165,14 @@ public static void GetVectorAndItemType(MemberInfo memberInfo, out bool isVector
/// and also the associated data type for this type. If a valid data type could not
/// be determined, this will throw.
/// </summary>
/// <param name="rawType">The type of the variable to inspect.</param>
/// <param name="name">The name of the variable to inspect.</param>
/// <param name="rawType">The type of the variable to inspect.</param>
/// <param name="attributes">Attribute of <paramref name="rawType"/>. It can be <see langword="null"/> if attributes don't exist.</param>
/// <param name="isVector">Whether this appears to be a vector type.</param>
/// <param name="itemType">
/// The corresponding <see cref="PrimitiveDataViewType"/> RawType of the type, or items of this type if vector.
/// </param>
public static void GetVectorAndItemType(Type rawType, string name, out bool isVector, out Type itemType)
public static void GetVectorAndItemType(string name, Type rawType, IEnumerable<Attribute> attributes, out bool isVector, out Type itemType)
{
// Determine whether this is a vector, and also determine the raw item type.
isVector = true;
Expand All @@ -185,9 +186,12 @@ public static void GetVectorAndItemType(Type rawType, string name, out bool isVe
isVector = false;
}

// The internal type of string is ReadOnlyMemory<char>. That is, string will be stored as ReadOnlyMemory<char> in IDataView.
if (itemType == typeof(string))
itemType = typeof(ReadOnlyMemory<char>);
else if (!itemType.TryGetDataKind(out _))
// Check if the itemType extracted from rawType is supported by ML.NET's type system.
// It must be one of either ML.NET's pre-defined types or custom types registered by the user.
else if (!itemType.TryGetDataKind(out _) && !DataViewTypeManager.Knows(itemType, attributes))
throw Contracts.ExceptParam(nameof(rawType), "Could not determine an IDataView type for member {0}", name);
}

Expand Down Expand Up @@ -242,7 +246,7 @@ public static InternalSchemaDefinition Create(Type userType, SchemaDefinition us
var parameterType = col.ReturnType;
if (parameterType == null)
throw Contracts.ExceptParam(nameof(userSchemaDefinition), "No return parameter found in computed column.");
GetVectorAndItemType(parameterType, "returnType", out isVector, out dataItemType);
GetVectorAndItemType("returnType", parameterType, null, out isVector, out dataItemType);
}
// Infer the column name.
var colName = string.IsNullOrEmpty(col.ColumnName) ? col.MemberName : col.ColumnName;
Expand Down
4 changes: 4 additions & 0 deletions src/Microsoft.ML.Data/DataView/TypedCursor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,10 @@ private Action<TRow> GenerateSetter(DataViewRow input, int index, InternalSchema

del = CreateDirectSetter<int>;
}
else if (DataViewTypeManager.Knows(colType))
{
del = CreateDirectSetter<int>;
}
else
{
// REVIEW: Is this even possible?
Expand Down
15 changes: 9 additions & 6 deletions src/Microsoft.ML.Data/Utils/ApiUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using Microsoft.ML.Data;
Expand All @@ -16,14 +18,15 @@ namespace Microsoft.ML

internal static class ApiUtils
{
private static OpCode GetAssignmentOpCode(Type t)
private static OpCode GetAssignmentOpCode(Type t, IEnumerable<Attribute> attributes)
{
// REVIEW: This should be a Dictionary<Type, OpCode> based solution.
// DvTypes, strings, arrays, all nullable types, VBuffers and RowId.
if (t == typeof(ReadOnlyMemory<char>) || t == typeof(string) || t.IsArray ||
(t.IsGenericType && t.GetGenericTypeDefinition() == typeof(VBuffer<>)) ||
(t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>)) ||
t == typeof(DateTime) || t == typeof(DateTimeOffset) || t == typeof(TimeSpan) || t == typeof(DataViewRowId))
t == typeof(DateTime) || t == typeof(DateTimeOffset) || t == typeof(TimeSpan) ||
Comment thread
wschin marked this conversation as resolved.
t == typeof(DataViewRowId) || DataViewTypeManager.Knows(t, attributes))
{
return OpCodes.Stobj;
}
Expand Down Expand Up @@ -56,7 +59,7 @@ internal static Delegate GeneratePeek<TOwn, TRow>(InternalSchemaDefinition.Colum
case FieldInfo fieldInfo:
Type fieldType = fieldInfo.FieldType;

var assignmentOpCode = GetAssignmentOpCode(fieldType);
var assignmentOpCode = GetAssignmentOpCode(fieldType, fieldInfo.GetCustomAttributes());
Func<FieldInfo, OpCode, Delegate> func = GeneratePeek<TOwn, TRow, int>;
var methInfo = func.GetMethodInfo().GetGenericMethodDefinition()
.MakeGenericMethod(typeof(TOwn), typeof(TRow), fieldType);
Expand All @@ -65,7 +68,7 @@ internal static Delegate GeneratePeek<TOwn, TRow>(InternalSchemaDefinition.Colum
case PropertyInfo propertyInfo:
Type propertyType = propertyInfo.PropertyType;

var assignmentOpCodeProp = GetAssignmentOpCode(propertyType);
var assignmentOpCodeProp = GetAssignmentOpCode(propertyType, propertyInfo.GetCustomAttributes());
Func<PropertyInfo, OpCode, Delegate> funcProp = GeneratePeek<TOwn, TRow, int>;
var methInfoProp = funcProp.GetMethodInfo().GetGenericMethodDefinition()
.MakeGenericMethod(typeof(TOwn), typeof(TRow), propertyType);
Expand Down Expand Up @@ -132,7 +135,7 @@ internal static Delegate GeneratePoke<TOwn, TRow>(InternalSchemaDefinition.Colum
case FieldInfo fieldInfo:
Type fieldType = fieldInfo.FieldType;

var assignmentOpCode = GetAssignmentOpCode(fieldType);
var assignmentOpCode = GetAssignmentOpCode(fieldType, fieldInfo.GetCustomAttributes());
Func<FieldInfo, OpCode, Delegate> func = GeneratePoke<TOwn, TRow, int>;
var methInfo = func.GetMethodInfo().GetGenericMethodDefinition()
.MakeGenericMethod(typeof(TOwn), typeof(TRow), fieldType);
Expand All @@ -141,7 +144,7 @@ internal static Delegate GeneratePoke<TOwn, TRow>(InternalSchemaDefinition.Colum
case PropertyInfo propertyInfo:
Type propertyType = propertyInfo.PropertyType;

var assignmentOpCodeProp = GetAssignmentOpCode(propertyType);
var assignmentOpCodeProp = GetAssignmentOpCode(propertyType, propertyInfo.GetCustomAttributes());
Func<PropertyInfo, Delegate> funcProp = GeneratePoke<TOwn, TRow, int>;
var methInfoProp = funcProp.GetMethodInfo().GetGenericMethodDefinition()
.MakeGenericMethod(typeof(TOwn), typeof(TRow), propertyType);
Expand Down
25 changes: 25 additions & 0 deletions src/Microsoft.ML.DataView/DataViewType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ private protected DataViewType(Type rawType)
// Object.Equals(Object) and GetHashCode. In classes below where Equals(ColumnType other)
// is effectively a referencial comparison, there is no need to override base class implementations
// of Object.Equals(Object) (and GetHashCode) since its also a referencial comparison.
/// <summary>
/// Return <see langword="true"/> if <see langword="this"/> is equivalent to <paramref name="other"/> and <see langword="false"/> otherwise.
/// </summary>
/// <param name="other">Another <see cref="DataViewType"/> to be compared with <see langword="this"/>.</param>
public abstract bool Equals(DataViewType other);
}

Expand Down Expand Up @@ -461,4 +465,25 @@ public override bool Equals(DataViewType other)

public override string ToString() => "TimeSpan";
}

/// <summary>
/// <see cref="DataViewTypeAttribute"/> should be used to decorated class properties and fields, if that class' instances will be loaded as ML.NET <see cref="IDataView"/>.
/// The function <see cref="Register"/> will be called to register a <see cref="DataViewType"/> for a <see cref="Type"/> with its <see cref="Attribute"/>s.
/// Whenever a value typed to the registered <see cref="Type"/> and its <see cref="Attribute"/>s, that value's type (i.e., a <see cref="DataViewSchema.Column.Type"/>)
/// in <see cref="IDataView"/> would be the associated <see cref="DataViewType"/>.
/// </summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public abstract class DataViewTypeAttribute : Attribute, IEquatable<DataViewTypeAttribute>
{
/// <summary>
/// A function implicitly invoked by ML.NET when processing a custom type. It binds a DataViewType to a custom type plus its attributes.
/// </summary>
public abstract void Register();

/// <summary>
/// Return <see langword="true"/> if <see langword="this"/> is equivalent to <paramref name="other"/> and <see langword="false"/> otherwise.
/// </summary>
/// <param name="other">Another <see cref="DataViewTypeAttribute"/> to be compared with <see langword="this"/>.</param>
public abstract bool Equals(DataViewTypeAttribute other);
Comment thread
wschin marked this conversation as resolved.
}
}
Loading