Open sandboxFocusImprove this doc

LAMA0530: A constructor parameter with the same name was already introduced

Severity Error
Reported by Metalama.Framework
Message The aspect '<AspectType>' cannot introduce the parameter '<Parameter>' into '<TargetDeclaration>' because an aspect has already introduced a parameter named '<ExistingParameterName>' into this constructor.

Cause

The IntroduceParameter method appends a parameter to a constructor. Before adding the parameter, Metalama checks whether the constructor already has a parameter with the same name:

  • If this parameter is declared in source code, Metalama doesn't report an error. It introduces the new parameter under another name, for instance p1 instead of p.
  • If this parameter was introduced by another aspect and has a compatible type, Metalama reuses it, or replaces its type with the more specific one.
  • If this parameter was introduced by the same aspect instance, or by another aspect with an incompatible type, Metalama reports LAMA0530.

The most common cause is an aspect that introduces several parameters into the same constructor and computes their names in a way that isn't unique. In the message, <TargetDeclaration> is the constructor, shown with the parameters that were already introduced.

In the following example, the InjectDependencies aspect introduces one parameter for each field marked with [Dependency] and derives the parameter name from the field type. Both fields have the type TextWriter, so the aspect tries to introduce two parameters named textWriter.

1using Metalama.Framework.Aspects;
2using Metalama.Framework.Code;
3using Metalama.Framework.Code.SyntaxBuilders;
4using System;
5using System.Linq;
6
7namespace Doc.LAMA0530.Error;
8
9[AttributeUsage( AttributeTargets.Field )]
10public class DependencyAttribute : Attribute { }
11
12public class InjectDependenciesAttribute : TypeAspect
13{
14    public override void BuildAspect( IAspectBuilder<INamedType> builder )
15    {
16        var fields = builder.Target.Fields
17            .Where( f => f.Attributes.OfAttributeType( typeof(DependencyAttribute) ).Any() )
18            .ToList();
19
20        foreach ( var constructor in builder.Target.Constructors )
21        {
22            foreach ( var field in fields )
23            {
24                // Error: the parameter name is derived from the field type, so two fields of the same type
25                // produce two parameters with the same name.
26                var typeName = ((INamedType) field.Type).Name;
27                var parameterName = char.ToLowerInvariant( typeName[0] ) + typeName.Substring( 1 );
28
29                builder.With( constructor ).IntroduceParameter( parameterName, field.Type, TypedConstant.Default( field.Type ) );
30
31                builder.With( constructor )
32                    .AddInitializer( StatementFactory.Parse( $"this.{field.Name} = {parameterName};" ) );
33            }
34        }
35    }
36}
37
1using System.IO;
2
3namespace Doc.LAMA0530.Error;
4
5[InjectDependencies]
6internal partial class ReportGenerator
7{
8    // Both fields have the type TextWriter.
9    [Dependency]
10    private readonly TextWriter? _auditLog;
11
12    [Dependency]
13    private readonly TextWriter? _errorLog;
14
    Error LAMA0530: The aspect 'InjectDependencies' cannot introduce the parameter 'textWriter' into 'ReportGenerator.ReportGenerator(TextWriter?)' because an aspect has already introduced a parameter named 'textWriter' into this constructor.

15    public ReportGenerator() { }
16}
17

Resolution

Make sure that the aspect gives a unique name to each parameter it introduces into the same constructor. For instance, derive the name from the name of the field instead of its type, as in the following example.

If two different aspects introduce a parameter with the same name, give the parameters compatible types when they represent the same dependency, so that Metalama reuses the parameter, or different names otherwise.

1using Metalama.Framework.Aspects;
2using Metalama.Framework.Code;
3using Metalama.Framework.Code.SyntaxBuilders;
4using System;
5using System.Linq;
6
7namespace Doc.LAMA0530.Fixed;
8
9[AttributeUsage( AttributeTargets.Field )]
10public class DependencyAttribute : Attribute { }
11
12public class InjectDependenciesAttribute : TypeAspect
13{
14    public override void BuildAspect( IAspectBuilder<INamedType> builder )
15    {
16        var fields = builder.Target.Fields
17            .Where( f => f.Attributes.OfAttributeType( typeof(DependencyAttribute) ).Any() )
18            .ToList();
19
20        foreach ( var constructor in builder.Target.Constructors )
21        {
22            foreach ( var field in fields )
23            {
24                // Fixed: the parameter name is derived from the field name, which is unique in the type.
25                var parameterName = field.Name.TrimStart( '_' );
26
27                builder.With( constructor ).IntroduceParameter( parameterName, field.Type, TypedConstant.Default( field.Type ) );
28
29                builder.With( constructor )
30                    .AddInitializer( StatementFactory.Parse( $"this.{field.Name} = {parameterName};" ) );
31            }
32        }
33    }
34}
35
Source Code
1using System.IO;
2
3namespace Doc.LAMA0530.Fixed;
4
5[InjectDependencies]
6internal partial class ReportGenerator
7{
8    // Both fields have the type TextWriter.
9    [Dependency]
10    private readonly TextWriter? _auditLog;
11
12    [Dependency]
13    private readonly TextWriter? _errorLog;
14
15    public ReportGenerator() { }
16}
17
Transformed Code
1using System.IO;
2
3namespace Doc.LAMA0530.Fixed;
4
5[InjectDependencies]
6internal partial class ReportGenerator
7{
8    // Both fields have the type TextWriter.
9    [Dependency]
10    private readonly TextWriter? _auditLog;
11
12    [Dependency]
13    private readonly TextWriter? _errorLog;
14
15    public ReportGenerator(TextWriter? auditLog = default, TextWriter? errorLog = default)
16    {
17        this._auditLog = auditLog; this._errorLog = errorLog;
18    }
19}
20

See also