// Unity ships annotations, but they're out of date. Specifically, the [PublicAPI] attribute is // defined with [MeansImplicitUse] instead of [MeansImplicitUse(ImplicitUseTargetFlags.WithMembers)] // So if applied to a class, only the class is marked as in use, while the members aren't. This // provider will apply [UsedImplicitly(ImplicitUseTargetFlags.WithMembers)] to any type that // has the old [PublicAPI] applied private static bool GetPublicAPIImplicitlyUsedAttribute(IClrDeclaredElement element, out ICollection <IAttributeInstance> collection) { collection = EmptyList <IAttributeInstance> .InstanceList; if (!(element is ITypeElement type) || !element.IsFromUnityProject()) { return(false); } foreach (var attributeInstance in type.GetAttributeInstances(ourPublicAPIAttribute, false)) { var attributeType = attributeInstance.GetAttributeType(); if (!(attributeType.Resolve().DeclaredElement is ITypeElement typeElement)) { continue; } var meansImplicitUse = typeElement.GetAttributeInstances(ourMeansImplicitUseAttribute, false) .FirstOrDefault(); if (meansImplicitUse?.Constructor == null || !meansImplicitUse.Constructor.IsDefault) { continue; } var flagsType = TypeFactory.CreateTypeByCLRName(ourImplicitUseTargetFlags, element.Module); collection = new[] { new SpecialAttributeInstance( ourUsedImplicitlyAttributeFullName, element.Module, () => new[] { new AttributeValue(new ConstantValue(3, flagsType)) } ) }; return(true); } return(false); }
// Treat Unity's RangeAttribute as ReSharper's ValueRangeAttribute annotation private bool GetValueRangeAttribute(IClrDeclaredElement element, out ICollection <IAttributeInstance> collection) { collection = EmptyList <IAttributeInstance> .InstanceList; if (!(element is IField field) || !element.IsFromUnityProject()) { return(false); } if (!myUnityApi.IsSerialisedField(field)) { return(false); } // Integer value analysis only works on integers, but it will make use of annotations applied to values that // are convertible to int, such as byte/sbyte and short/ushort. It doesn't currently use values applied to // uint, or long/ulong, but it is planned, so we'll apply to all sizes of integer. var predefinedType = myPredefinedTypeCache.GetOrCreatePredefinedType(element.Module); if (!Equals(field.Type, predefinedType.Int) && !Equals(field.Type, predefinedType.Uint) && !Equals(field.Type, predefinedType.Long) && !Equals(field.Type, predefinedType.Ulong) && !Equals(field.Type, predefinedType.Short) && !Equals(field.Type, predefinedType.Ushort) && !Equals(field.Type, predefinedType.Byte) && !Equals(field.Type, predefinedType.Sbyte)) { return(false); } foreach (var attributeInstance in field.GetAttributeInstances(KnownTypes.RangeAttribute, false)) { // Values are floats, but applied to an integer field. Convert to integer values var unityFrom = attributeInstance.PositionParameter(0); var unityTo = attributeInstance.PositionParameter(1); if (!unityFrom.IsConstant || !unityFrom.ConstantValue.IsFloat() || !unityTo.IsConstant || !unityTo.ConstantValue.IsFloat()) { continue; } // The check above means this is not null. We take the floor, because that's how Unity works. // E.g. Unity's Inspector treats [Range(1.7f, 10.9f)] as between 1 and 10 inclusive var from = Convert.ToInt64(Math.Floor((float)unityFrom.ConstantValue.Value.NotNull())); var to = Convert.ToInt64(Math.Floor((float)unityTo.ConstantValue.Value.NotNull())); collection = CreateRangeAttributeInstance(element, predefinedType, from, to); return(true); } foreach (var attributeInstance in field.GetAttributeInstances(KnownTypes.MinAttribute, false)) { var unityMinValue = attributeInstance.PositionParameter(0); if (!unityMinValue.IsConstant || !unityMinValue.ConstantValue.IsFloat()) { continue; } // Even though the constructor for ValueRange takes long, it only works with int.MaxValue var min = Convert.ToInt64(Math.Floor((float)unityMinValue.ConstantValue.Value.NotNull())); var max = int.MaxValue; collection = CreateRangeAttributeInstance(element, predefinedType, min, max); return(true); } return(false); }