The function-return rule `data_type_or_implicit_plus_id` parses dimensions
following a named return type with `index_components_opt` and converts them
to `dimensions` in its action.
Use `dimensions_opt` directly, which performs the same conversion through
`make_dimensions()`. This makes the index-component overload of
`pform_make_parray_type()` unnecessary. Remove it together with the unused
`set_type_id_range()` overload and the `nullptr` disambiguation overload.
The shared port/declaration path already gets its type dimensions from
`ps_type_identifier_dim`, so it needs no corresponding change. This
simplifies the grammar in preparation for removing `TYPE_IDENTIFIER`.
Signed-off-by: Lars-Peter Clausen <[email protected]>
Verilog and SystemVerilog require parentheses on every module instance,
even when the module has no ports (IEEE 1364-2005 12.1.2 and IEEE 1800-2023
23.3.2). Omitting them is not valid in any standard version.
Icarus accepts arrayed instances such as `M m[1:0];` as an extension added
by commit 2d498363d7 ("Handle instance array of port-less modules.").
Check that other parentheses-less forms, such as `M m;` and
`M a[1:0], b, c();`, remain rejected to avoid further deviations from the
standard. Accepting scalar instances without parentheses would also
conflict with SystemVerilog variable declarations such as `M m;`, where
`M` names a type.
Also check that the existing array extension and mixed instance lists
such as `M a[1:0], b(), c[1:0];` remain accepted.
Signed-off-by: Lars-Peter Clausen <[email protected]>
Check that undefined named parameter types fail during elaboration for a
package-qualified declaration in a module body and for an unqualified
parameter port declaration that omits the `parameter` keyword.
Signed-off-by: Lars-Peter Clausen <[email protected]>
A parameter declaration can start with its name (`parameter T = 1`) or a
named type (`parameter T P = 1`). If dimensions follow the first identifier,
they can be unpacked dimensions of the parameter or packed dimensions of the
type. The parser must see whether another identifier follows to distinguish
these forms.
In preparation for removing `TYPE_IDENTIFIER`, keep the identifier, dimensions
and possible parameter name together until the grammar has enough lookahead.
Build an unresolved named type for the typed form and leave type validation to
elaboration.
Keep implicit types out of parameter port list items that omit the `parameter`
keyword.
Signed-off-by: Lars-Peter Clausen <[email protected]>
A function prototype's first identifier can be its name (`function T()`) or
its return type (`function T f()`). Parse the optional return type and name
together for extern class methods and modport function prototypes, as for
ordinary function declarations.
This is in preparation for removing TYPE_IDENTIFIER. Remove the unused
data_type_or_implicit_or_void rule.
Extern class methods and modport task/function ports remain unsupported.
Signed-off-by: Lars-Peter Clausen <[email protected]>
A let port can start with its name (`let l(T) = T;`) or a named type
(`let l(T value) = value;`). Parse the optional type, name and dimensions
together using the existing declaration rule.
This is in preparation for removing TYPE_IDENTIFIER. Keep the explicit
`untyped` alternative separate and remove the unused let_formal_type rule.
Let declarations remain unsupported.
Signed-off-by: Lars-Peter Clausen <[email protected]>
Check that an unresolved named function return type is diagnosed during
elaboration rather than being rejected while parsing.
Signed-off-by: Lars-Peter Clausen <[email protected]>
In preparation for removing `TYPE_IDENTIFIER` from the parser, function
declarations must handle type names as ordinary identifiers. The grammar can
then no longer use the token kind to distinguish these forms:
function T;
function T f;
The first form declares a function named T with an implicit return type. The
second uses T as the return type and declares f. Keep the leading identifier,
its optional packed dimensions and the following identifier in the function
rule until enough input has been seen to choose between the two forms.
Build an unresolved named type for the typed form and defer lookup to
elaboration. The return type is therefore only checked to name a type during
elaboration. Whether an identifier names a type can depend on elaborated
context, such as a base class, so the parser cannot validate it. Invalid named
return types are still diagnosed as required types during elaboration. Add the
index component helpers needed by this and the following ambiguity fixes.
Named return types require SystemVerilog. Enter parser error recovery for the
two-identifier form in older language modes so it remains rejected and parsing
can continue after the invalid function.
Signed-off-by: Lars-Peter Clausen <[email protected]>
`type_id_range` stores the individual fields of the declaration identifier
location. Consumers that need the complete location have to reconstruct it.
Store the `vlltype` directly instead. This also keeps the declaration grammar
helpers from having to enumerate every location field.
Signed-off-by: Lars-Peter Clausen <[email protected]>
A function declaration creates a nested scope whose name is visible throughout
its containing scope. Check that a later function named `T` hides an outer
typedef while elaborating an earlier signal, a class property, and a typed
constructor.
These consumers take different paths after type lookup. Verify that each
reports the failed lookup without asserting or dereferencing the missing type.
Signed-off-by: Lars-Peter Clausen <[email protected]>
Check that an identifier that can denote either a type or a value selects a
later variable with `-gno-strict-net-var-declaration`. Check that a name in a
declaration that requires a data type still uses the earlier visible typedef.
Signed-off-by: Lars-Peter Clausen <[email protected]>
`typeref_t` stores the `typedef_t` selected by the parser. This binds a type
identifier before the elaborated scope is available and can select the wrong
declaration. Function and task declarations are visible throughout their
scope, so a later subroutine named `T` in the current scope hides an earlier
`T` type in an outer scope even when the type reference appears before the
subroutine declaration. Parser-time lookup can not handle this correctly.
For example:
typedef int T;
module test;
T value;
function T;
T = 0;
endfunction
endmodule
The declaration of `value` must be rejected because the local function named
`T` hides the outer typedef. The same applies to a task named `T`.
Type lookup can also depend on information that is not available to the parser.
For example, a parameterized class can extend a type parameter and inherit `T`
from the selected base class:
class Derived #(type B = DefaultBase) extends B;
T value;
endclass
Parameterized classes are not supported yet, but resolving named types during
elaboration is a prerequisite for supporting this case.
Replace `typeref_t` with `type_identifier_t`, which owns a `PEIdent`. Construct
the identifier in the parser, then resolve it through
`PEIdent::elaborate_type()` and `symbol_search()` while elaborating the data
type. Parser-time type lookup is only used to select grammar paths.
`pform_new_ident()` preserves wildcard import activation for the identifier
expression.
Enforce declaration ordering when an identifier occurs in a grammar position
that requires a data type, even when compatibility options relax it for
ordinary variable lookup. A later variable therefore does not hide an outer
type in a declaration, and a later type declaration can not satisfy an earlier
use. Keep ordinary lookup when an identifier can be either a type or a value,
such as an argument to `$bits()`. Add
`SYMBOL_SEARCH_STRICT_DECLARATION_ORDER` and a required-type elaboration context
for the former case.
For example:
typedef logic [7:0] T;
module test;
T value;
localparam int A = $bits(value);
localparam int B = $bits(T);
integer T;
endmodule
The declaration of `value` uses the outer typedef, so `A` is 8. With relaxed
variable declaration ordering, the ambiguous reference in `$bits(T)` uses the
later integer, so `B` is 32.
Check a constant's declaration position before `get_parameter()` elaborates it
on demand when declaration order is enforced. This keeps a later declaration in
`parameter T T` from recursively selecting itself as the type.
Recover a failed deferred lookup in `type_identifier_t` with a scalar logic type
after reporting the error. This keeps the existing non-null expectations of
type consumers unchanged.
Signed-off-by: Lars-Peter Clausen <[email protected]>
Check that a class can derive from a forward declared base class that is
defined later, both directly and through a typedef alias. Cover a multi-level
hierarchy, base constructor chaining, and inherited property layout.
Check that a local forward declaration hides an outer class with the same name.
Check forward declared base classes in package scopes and in separate instances
of the same module. Check that inheritance cycles, undefined forward
declarations, and non-class base types are rejected.
Run the tests through the native and vlog95 backends.
Signed-off-by: Lars-Peter Clausen <[email protected]>
SystemVerilog allows a forward declared class to be used as the base of a
class that is declared before the base class definition.
typedef class B;
class C extends B;
endclass
class B;
endclass
The base type is currently elaborated while creating the derived class. At
that point the base class has not been added to the scope yet and the base
type does not resolve to a class.
Create all classes first and bind their base classes in a second pass. Reject
inheritance cycles before attaching the base class.
Signed-off-by: Lars-Peter Clausen <[email protected]>
Check that `SCOPE.value` resolves to a forward-referenced generate block when
`SCOPE` also names an imported type. The lexer can only see the type at the
point of the reference, so this exercises parsing a `TYPE_IDENTIFIER` as the
first hierarchy component.
Check that a compilation-unit type does not prevent a same-named inherited
class property from being used as a procedural l-value or expression.
Check that empty and queue-bound indices on such hierarchy components are
rejected during parsing instead of reaching elaboration.
Signed-off-by: Lars-Peter Clausen <[email protected]>
The lexer returns `TYPE_IDENTIFIER` when an identifier matches a visible
typedef. It does not know about hierarchy scopes declared later, so an
imported type can prevent a forward-referenced generate block with the same
name from being used as the first hierarchy component:
package p;
typedef int SCOPE;
endpackage
import p::*;
module test;
initial SCOPE.value = 1;
if (1) begin : SCOPE
int value;
end
endmodule
The same applies to inherited class properties, which are not visible to the
parser:
typedef int value;
class Base;
int value;
endclass
class Derived extends Base;
function int get;
return value;
endfunction
endclass
Accept `TYPE_IDENTIFIER` as the first hierarchy component and build it through
the same path as `IDENTIFIER`. This applies the existing hierarchy-index
validation before lookup and keeps malformed components out of elaboration.
Named types in expression-like contexts are already resolved through
identifier elaboration, so remove their separate `type_value` alternatives and
use the common hierarchy expression path. Keep atomic types in `type_value`.
This removes the grammar overlap without adding dedicated parameter and system
argument rules.
These identifiers can now reach continuous assignment l-value elaboration.
Use the common type diagnostic there. The implicit-net lookup checks typedefs,
so invalid type uses in continuous assignments, gate terminals, and module
port connections remain rejected rather than silently becoming nets.
This only permits hierarchy names to shadow visible type names. A hierarchical
path that resolves to a type remains rejected during elaboration.
Signed-off-by: Lars-Peter Clausen <[email protected]>
Check that local, imported, and package-scoped typedefs resolve as declaration
types and as `$bits()` arguments, including attached dimensions and function
return and argument types.
Check lexical visibility before and after a local typedef declaration and an
explicit import. Earlier references resolve to outer typedefs and later
references resolve to the newly visible types.
Signed-off-by: Lars-Peter Clausen <[email protected]>
For-loop variable declarations currently match an optional `var` rule
before the data type. If `TYPE_IDENTIFIER` is also allowed to start a
hierarchy identifier, the parser can either shift it as an lvalue or
reduce the empty `K_var_opt` before a declaration.
Fold the optional keyword into `for_decl_data_type` with separate
alternatives for a data type with and without `var`. This keeps
declaration behavior unchanged and prepares for hierarchy identifiers
shadowing typedef names without introducing that shift/reduce conflict.
Signed-off-by: Lars-Peter Clausen <[email protected]>
Check that a type identifier is rejected rather than declared as an implicit
net when used in a continuous assignment, primitive terminal, or ordered
module port connection. Cover local typedefs, explicit imports, and activated
wildcard imports.
These contexts are currently rejected by the grammar. Keep the tests in place
when type and hierarchy identifiers start using the same parser path.
Signed-off-by: Lars-Peter Clausen <[email protected]>
`PEIdent::declare_implicit_nets()` checks visible nets, parameters, genvars,
events, and enum constants before creating an implicit net, but does not check
typedefs. Type identifiers are currently filtered by the grammar before they
can reach this code, but this is not true when type and hierarchy identifier
parsing use the same path.
Check local and imported typedefs while searching the lexical scopes. Stop the
type search when another local declaration hides an outer typedef.
Signed-off-by: Lars-Peter Clausen <[email protected]>
Check that package tasks can be called as statements both with and
without an argument list. Use a caller variable that shares its name
with a package typedef to verify that call arguments are parsed in the
caller context.
Also check package-scoped void functions and void casts of non-void
functions. Cover class and queue methods whose receiver is a package
member as well.
Signed-off-by: Lars-Peter Clausen <[email protected]>
SystemVerilog allows package tasks and functions to be called as
statements through package-qualified names, for example:
p::run();
`subroutine_call` currently accepts ordinary hierarchical and
class-qualified names, but not `package_scope`, so the parser rejects
these calls.
Accept a package scope before the hierarchical subroutine name. Leave
package lexer mode before parsing arguments so argument names are
classified in the caller context, then retain the package on `PCallTask`
for elaboration.
Also retain the package when a package member is used as a method
receiver. This allows calls such as `p::object.run()` to resolve the
object in the package rather than the caller scope.
Signed-off-by: Lars-Peter Clausen <[email protected]>
Check a type cast with an inline packed dimension and reject dimensions
following a type identifier in a cast target. Use a dimensioned typedef in
the reject test to distinguish dimensions in the type from dimensions after
the identifier.
Check that an error reported while elaborating a cast-target type is emitted
only once when width checking and expression elaboration reuse the cached
target information. Also check that each module instance resolves a
type-parameter cast target using its own parameter value.
Check that typed dynamic-array elaboration constructs the explicit cast
target rather than the enclosing expression type.
Run the tests through the native and vlog95 backends.
Signed-off-by: Lars-Peter Clausen <[email protected]>
A SystemVerilog cast target can be either a type or a constant size
expression. Currently the parser commits to `PECastType` or `PECastSize`
based on how the target identifier is classified while parsing. This is too
early for identifiers whose meaning is only known after symbol lookup. As a
result, corner cases are handled incorrectly when parser-time classification
does not match the result of elaboration. For example, the parser can not
decide whether an identifier inherited from a base class is a type or a
constant size expression. Supporting the inherited lookup is separate, but
the cast target must remain unresolved until elaboration for that lookup to
be used.
Parse both forms through `expr_primary_or_typename` and represent them with
one `PECast`. Keep atomic types wrapped in `PETypename` and preserve named
targets as `PEIdent`.
Resolve the target during elaboration. First use `test_type()` to distinguish
a type target from a constant size expression. For a type target, use a
contextual `elaborate_type()` call to resolve the type. Diagnose dimensions
after a type identifier directly from `PEIdent::elaborate_type()` when it is
used as a cast target. A failed type elaboration returns `nullptr` and does
not fall back to interpreting the target as a size expression. Dimensions
contained in the named type remain valid.
Cache the resolved target information during width checking because ordinary
expression elaboration needs the same information. Tag the cache with the
`NetScope` and recompute it when the scope changes since type parameters can
give the same cast expression a different target type in each instance.
Typed elaboration can bypass width checking, so resolve into a local value
when no matching cache is available. This avoids modifying the parsed
expression from a const elaboration method while still avoiding repeated
diagnostics between the normal width checking and expression elaboration
phases.
Share the type and size conversion paths between width-based and typed
elaboration. Use the explicit cast target when constructing a dynamic array
instead of the enclosing expression type. Own the cast target and operand
with `std::unique_ptr`.
Signed-off-by: Lars-Peter Clausen <[email protected]>
Check that packed dimensions are preserved when local and
package-qualified identifiers resolve to types in type parameter values and
system function arguments. Verify that symbolic dimensions are evaluated in
the scope of each module instance.
Check the diagnostics for unsized and queue suffixes, indexed part selects,
and packed dimensions applied to an unpacked named type.
Signed-off-by: Lars-Peter Clausen <[email protected]>
Check that lookup chooses the nearest visible type or value when an
identifier can denote either. Cover lexical ordering, inherited properties,
and a bracket suffix that must remain a value select after lookup.
Check typed expression and l-value contexts where a type must be rejected
instead of binding an outer value. Check that a hierarchical type reference
is rejected with the type-specific diagnostic.
Check that probing an invalid hierarchical type parameter does not elaborate
its signal prefix and report a false circular dependency.
Signed-off-by: Lars-Peter Clausen <[email protected]>
Named types in expression-like contexts are currently converted to
`PETypename` by the parser. This commits to the type interpretation before
symbol lookup, although the same syntax can represent an ordinary identifier.
Keep atomic types as `PETypename`, but parse local and package-qualified type
identifier candidates as `PEIdent`. Preserve any bracket suffixes so they can
be interpreted as packed dimensions for a type or as selects for a value.
Add `PExpr::test_type()` for non-elaborating type detection and
`PExpr::elaborate_type()` to return the elaborated type. Use these interfaces
for `$bits()`, `$sizeof()` and type parameters. Callers test the
interpretation before elaborating it, so a null type reports an elaboration
failure without incorrectly falling back to value elaboration.
Type testing is normally followed by elaboration, and both operations need
the same symbol lookup. Let `PEIdent::test_type()` cache the result and its
declaration scope, keyed by the lookup scope. Keep `elaborate_type()` const and
perform a local lookup when type testing was skipped or the cached result is
from another scope. This avoids repeated lookup without reusing a
scope-dependent result.
Search the complete identifier path before deciding whether it names a type.
Stop lookup at signal placeholders so they still hide matching types in outer
scopes without being elaborated by the type probe.
The LRM section 6.18 does not allow hierarchical references to type
identifiers, so reject a dotted type reference after lookup and report where
the type was declared. This distinguishes an invalid `test.T` type reference
from a path that does not name a type.
Use the unified symbol lookup result to choose the type or value
interpretation. Report an error when a type reaches an expression or l-value
context. This preserves normal lexical shadowing while deferring only the
syntactic decision.
Signed-off-by: Lars-Peter Clausen <[email protected]>
`symbol_search()` currently uses a boolean argument to allow forward
references to the terminal name. Additional lookup options would require more
boolean arguments and make call sites difficult to read.
Replace the boolean with a bitmask and use a named flag at the existing
forward-reference call sites. Add a second flag that stops lookup at an
unelaborated signal instead of elaborating it. Stopping the lookup makes the
signal continue to hide matching symbols in outer scopes.
Forward-reference permission applies only to the terminal name, while signal
elaboration suppression applies to the complete path. This allows callers
that only need to classify a name to avoid triggering signal elaboration
during lookup.
Signed-off-by: Lars-Peter Clausen <[email protected]>
Check that a visible type prevents lookup from continuing to an outer value
for function and task calls and implicit `.name` and wildcard `.*` port
connections. Check that types are rejected as disable targets, named events,
procedural l-values, and expressions elaborated with a required type.
Also check the opposite lexical-order case where a typedef declared after a
reference does not hide an outer named event or variable. Use diagnostic gold
files for each negative case that emits the type-specific error.
Signed-off-by: Lars-Peter Clausen <[email protected]>
SystemVerilog places user-defined types and data identifiers in the same
namespace. Symbol lookup cannot currently return a type, so a value lookup can
skip a nearer typedef and incorrectly bind a value in an outer scope when an
ambiguous identifier is resolved during elaboration.
Add typedef results to `symbol_search()` and check visible typedefs before
continuing to enclosing scopes. Use the lexical visibility limit selected by
the caller. This is normally the reference position, while function and task
call lookup uses the end of the current scope as required by the LRM section
26.3.
Let callers that require a value or another non-type symbol diagnose the type
result and report its declaration location. This includes function and task
calls, expressions with and without a required type, procedural l-values,
named-event triggers, and disable targets. Callers that only probe for a
particular kind of symbol, including port-connection matching, ignore a type
result. Also ensure scope-only callers do not confuse the typedef's owning scope
with the resolved object.
This prepares expression elaboration to defer the type-versus-value decision
while preserving lexical shadowing.
Signed-off-by: Lars-Peter Clausen <[email protected]>
Check separately that a local variable, named event, and parameter hide a
compilation-unit task used as a disable target.
Signed-off-by: Lars-Peter Clausen <[email protected]>
Disable targets currently use `Design::find_scope()`, which searches only
scope names. A task or named block from an outer scope is therefore selected
even when a variable, named event, or parameter in a closer scope has the
same name.
The LRM section 3.13 places tasks, named blocks, parameters, named events, and
variables in the same local name space. Use normal symbol lookup and require
the result to be a scope. This lets a closer non-scope object hide the outer
disable target and reports an error for the non-scope result.
Signed-off-by: Lars-Peter Clausen <[email protected]>
Check that a closer variable, named event, or parameter hides a task or
function used as a statement. Also check object method lookup and recursive
function calls used as statements.
Check that a later compilation-unit task or function takes precedence over a
matching subroutine in an enclosing instance. Check that different instances
of the same module resolve task calls through their respective enclosing
instances. Check ordinary lexical ordering for a task-call receiver declared
before or after the reference.
Signed-off-by: Lars-Peter Clausen <[email protected]>
Task and function calls used as statements currently use
`Design::find_task()` and `Design::find_function()`, which only search for
scopes of the requested type. A closer variable, named event, or parameter is
skipped. This can call a hidden task or function, and can make the non-void
discarded-return path resolve a different symbol and abort.
Use one `symbol_search()` for both task lookup and the function fallback so
all declarations in the shared name space participate in lookup. Accept a
function scope or the return variable of a recursive function call, while
preserving the existing method fallback.
Allow the terminal task or function name to be declared later while resolving
a receiver prefix at the call position. Use the call position when the method
fallback searches for the receiver as well.
Remove the now unused `Design::find_task()` and `Design::find_function()`
helpers.
Signed-off-by: Lars-Peter Clausen <[email protected]>
Check that same-named scopes in the enclosing instance hierarchy do not
interfere with lookup of compilation-unit variables, parameters, named events,
and imported variables. Cover expressions, procedural l-values, implicit and
wildcard port connections, and lookup from module and nested function scopes.
Check task and function calls, including method receivers and chained calls.
Verify that receiver prefixes follow ordinary lexical ordering while direct,
imported, and chained compilation-unit function names can be declared after
their references. Check the enclosing-instance fallback when the compilation
unit has no match.
Signed-off-by: Lars-Peter Clausen <[email protected]>
`symbol_search()` currently searches the enclosing instance hierarchy before
the compilation-unit scope for ordinary names. An enclosing instance scope can
therefore hide an earlier compilation-unit variable or object, including when
the object is the prefix of a dotted reference.
The LRM section 3.12.1 requires a common lookup order. First search the local
lexical scopes through the design unit. Next search the compilation-unit scope
up to the reference position. If there is no match, resume at the saved
instantiation parent and continue through the instance hierarchy. Disable data
object lookup when resuming so only enclosing scope names can match.
Task and function names follow the same traversal, but the LRM section 23.8.1
lets the terminal subroutine name search the complete compilation unit. Use a
separate terminal lexical limit for this. Resolve a prefix such as `object` in
`object.func()` at the reference position while allowing a forward declaration
of `func`.
Signed-off-by: Lars-Peter Clausen <[email protected]>
The scope traversal clears `start_scope` after switching to the compilation
unit and uses the null value to prevent a second compilation-unit search.
This gives the scope used for path evaluation two separate purposes.
Track whether the compilation-unit scope has been searched with a separate
flag. Keep the existing instance-before-compilation-unit traversal order.
Signed-off-by: Lars-Peter Clausen <[email protected]>
The scope traversal tracks whether it has crossed a module boundary and
uses the inverse of that state to decide whether objects are visible.
Track object visibility directly instead. This keeps the existing behavior
while making the module-boundary rule and later traversal changes easier to
follow.
Signed-off-by: Lars-Peter Clausen <[email protected]>
Child scopes and interface aliases are matched directly in the scope
traversal. This obscures the code that decides which scope to search next.
Move the child scope and interface alias lookup into a helper. Keep the
existing lookup order and error handling unchanged so the traversal can be
restructured separately.
Signed-off-by: Lars-Peter Clausen <[email protected]>
The object lookup for each scope is embedded in the scope traversal. This
makes the traversal order and the module boundary handling difficult to
follow.
Move net, event, parameter, class property, and placeholder lookup into a
helper. Use separate found, not found, and failure results so the existing
lookup behavior is preserved. This prepares the scope traversal to be
changed independently.
Signed-off-by: Lars-Peter Clausen <[email protected]>
Check that a class property cannot share a name with another property, a
function, a task, a parameter, a type, or an enum named constant. Check
duplicate properties in both separate and comma-separated declarations. The
latter guards the shared declaration type ownership that previously caused a
crash.
Exercise properties in both declaration orders so both member registration
paths are covered.
Signed-off-by: Lars-Peter Clausen <[email protected]>
The LRM requires an identifier to name only one item within a scope. Class
properties are currently stored only in `class_type_t::properties`, outside
the common local symbol table. As a result, declarations such as:
class C;
int value;
function int value;
return 0;
endfunction
endclass
are accepted. A second property in a separate declaration silently replaces
the first property map entry. A repeated name in a comma-separated declaration
such as `int value, value` can crash because both declarators share the same raw
type.
Make `prop_info_t` a named item and register each property in the class local
symbol table. Check the declaration before taking ownership of its type or
initializer, since comma-separated properties share the raw declaration type.
Use `emplace()` for the property map so an accepted property cannot replace an
existing entry.
Properties now take part in the common symbol lookup. Give them a distinct
symbol type so duplicate declaration diagnostics identify them as class
properties, and remove the separate property check from type-identifier
classification.
Signed-off-by: Lars-Peter Clausen <[email protected]>