-
Notifications
You must be signed in to change notification settings - Fork 135
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Quick fix for access properties by id instead of string #783
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me! Might be worth adding a couple more tests with property names beginning with underscore - I've seen this naming style used a lot in shaders. Make sure the field name get generated correctly, doesn't clash with existing name, etc.
9dd0fc0
to
77c1579
Compare
Add test with undescore name |
LGTM! |
{ | ||
var argument = expression.Arguments[argumentIndex]; | ||
var (clrName, methodName) = ourTypes[containingType.GetClrName()]; | ||
var literal = (argument.Expression as ILiteralExpression)?.ConstantValue.Value as string; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about string concatenation: M("part" + "secondPart")
or constant strings: const string constStr = "myString"; M(constStr)
?
This code won't work with any of this. I'm not sure whether not supporting them is a deliberate decision or an overlook, please add tests for these scenarios.
public class PreferAddressByIdToGraphicsParamsAnalyzer : UnityElementProblemAnalyzer<IInvocationExpression> | ||
{ | ||
|
||
private static readonly IDictionary<IClrTypeName, (IClrTypeName, string)> ourTypes = new Dictionary<IClrTypeName, (IClrTypeName, string)>() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this IDictionary<TKey, (TKey, TValue)>
really necessary? Seems very confusing and any change to this code will require understanding why this type was introduced.
Looks like the only usage var (clrName, methodName) = ourTypes[containingType.GetClrName()];
could be easily rewritten without it:
var clrName = containingType.GetClrName();
var methodName = ourTypes[clrName];
index = 0; | ||
containingType = null; | ||
|
||
if (!stringMethod.ShortName.StartsWith("Get") && !stringMethod.ShortName.StartsWith("Set")) return false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably worth introducing a variable for stringMethod.ShortName
since it's mentioned at least 4 times in this method.
return false; | ||
} | ||
|
||
private bool MatchSignatureStringToIntMethod(IMethod stringMethod, IMethod intMethod) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do these parameters allow nulls or not?
t.GetDeclaredElement() as IMethod
is passed from the predicate filter, so nulls can be passed in,
however the first line uses them without null checks: if (!stringMethod.ReturnType.Equals(intMethod.ReturnType)) return false;
so nulls will result in a NullReferenceException
.
Annotating members with [NotNul]
or [CanBeNull]
could have caught possible NREs like this one, please consider using them.
var intMethodParam = intMethodParameters[i]; | ||
var stringMethodParam = stringMethodParameters[i]; | ||
|
||
if (stringMethodParam.Type.IsString() && intMethodParam.Type.IsInt()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This allows methods with following signatures:
M(int firstId, int secondId)
M(string firstName, string secondName)
Probaby should only allow one int
to string
mismatch in parameters.
|
||
var factory = CSharpElementFactory.GetInstance(myInvocationExpression); | ||
|
||
var fieldInitializerValue = factory.CreateExpression("$0.$1($2)", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please move this code under the if (name == null)
as it's not required for other code paths
} | ||
|
||
// replace argument | ||
var argument = factory.CreateArgument(ParameterKind.VALUE, factory.CreateReferenceExpression(name)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like this change will break code with named arguments:
public class InvalidLiteralForProperyNameTest
{
public void Test(Material material)
{
material.SetFloat(value: 10.0f, name: "f{caret}loat");
}
}
Please add tests for such cases
|
||
|
||
// generate field if we need it. If we have field which addresses to same property reuse it. | ||
var name = TryFindField(myLiteral, classDeclaration, myTypeName, myMapFuntion); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd recommend using declared elements instead of strings. Relying on strings introduces additional bugs since created reference expression loses its context and might be accidently binded to another declared element that hides the desired one, e.g.:
public class TopLevelClass
{
private static int Test = Shader.PropertyToID("test");
public class NestedClass
{
private static string Test = "notTest";
public void Method(Material material)
{
material.SetFloat("te{caret}st", 10.0f);
}
}
}
I believe this test will use the nested class' Test
property which is not correct.
{ | ||
if (HasOverloadWithIntParameter(stringMethod, expression, out var argumentIndex, out var containingType)) | ||
{ | ||
var argument = expression.Arguments[argumentIndex]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please check all the invariants like having at least required number of arguments explicitly.
Current implemenation will work but it just barely misses bugs like a NullReferenceException
on an invocation like this:
var s = nameof(Method);
.
{ | ||
public class InvalidLiteralForProperyNameTest | ||
{ | ||
private static readonly int Property = Shader.PropertyToID("Property"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
material.SetFloat("f{caret}loat", 10.0f);`
is converted to
private static readonly int Property = Shader.PropertyToID("Property");
...
material.SetFloat({caret}Property, 10.0f);
Are these code samples really equivalent?
string requiredTypeName, string requiredMethodName, out string name) | ||
{ | ||
name = null; | ||
foreach (var member in declaration.ClassMemberDeclarations) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code only checks declarations syntaxically located in the passed declaration.
I belive that my original example still won't reuse existing property, could you please add a test for it?
public partial class TestClass
{
public void Method(Material material)
{
material.SetFloat("te{caret}st", 10.0f);
}
}
public partial class TestClass
{
private static readonly int Test = Shader.PropertyToID("test");
}
You can check all declarations of a type like this
foreach (var declarationPart in declaration.DeclaredElement.GetDeclarations())
var factory = CSharpElementFactory.GetInstance(myInvocationExpression); | ||
|
||
// try find declaration where string to id conversation is done. If we don't find it, create in top-level class | ||
var idDeclaration = TryFindDeclaration(myInvocationExpression, myGraphicsPropertyName, myTypeName, myMapFunction, out var name); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like out var name
is always overwritten before usage. Can we get rid of it?
c46c70e
to
45c2df2
Compare
…load and generate field with correct id for the string.
…f constants and literals. Reused fields/properties is searching not only in top-level class. Fix bug with wrong reference. Add tests for all new cases.
45c2df2
to
cfb8133
Compare
Access properties in
Shader
,Material
,Animator
byint
is faster than usingstring
overload.The property IDs created from string hashes are deterministic over the course of a single run, so this quick fix creates a private static readonly field in class and reuse this field for overload.
Example:
is transformed to: