Repository: ycm-core/ycmd

Issue:
module support for objective-c
I noticed the latest `YouCompleteMe` now supports `clang 3.9.0`.
So I did a little experiment (https://github.com/haifengkao/libclang-experiments-py).
To my surprise, the `libclang` built by `ycmd` can handle the objective-c modules successfully.

But `:YcmDiag` still fails with the annoying error `could not build module 'Foundation'`.
![2016-12-02 1 45 01](https://cloud.githubusercontent.com/assets/4080524/20805340/3004df74-b831-11e6-8e3f-fc52a1476dd7.jpg)

Since `libclang` now supports objective modules, what should I do to make it work with `YouCompleteMe`?

*update*
The YCM flags can be found in https://github.com/haifengkao/libclang-experiments-py/blob/master/.ycm_extra_conf.py


Discussion:
I managed to create a simple test case
```py
from pprint import pprint
from ycmd.tests.clang import IsolatedYcmd, PathToTestFile
from ycmd.tests.test_utils import BuildRequest

@IsolatedYcmd
def Diagnostics_Module_test( app ):
  contents = """
  @import Foundation;
"""
  flags = ['-x', 'objective-c',
          '-arch', 'arm64',
          '-fmodules',
          '-miphoneos-version-min=9.3',
          '-isysroot', '/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk',
          ]

  event_data = BuildRequest( compilation_flags = flags,
                             event_name = 'FileReadyToParse',
                             contents = contents,
                             filetype = 'objc' )

  results = app.post_json( '/event_notification', event_data ).json
  pprint (results)
```

The output is
```
[{u'fixit_available': False,
  u'kind': u'ERROR',
  u'location': {u'column_num': 11, u'filepath': u'/foo', u'line_num': 2},
  u'location_extent': {u'end': {u'column_num': 0,
                                u'filepath': u'',
                                u'line_num': 0},
                       u'start': {u'column_num': 0,
                                  u'filepath': u'',
                                  u'line_num': 0}},
  u'ranges': [{u'end': {u'column_num': 21,
                        u'filepath': u'/foo',
                        u'line_num': 2},
               u'start': {u'column_num': 4,
                          u'filepath': u'/foo',
                          u'line_num': 2}}],
  u'text': u"could not build module 'Foundation'"}]
.
----------------------------------------------------------------------
Ran 1 test in 0.367s

OK
```

I have tried `libclang` with C++ binding, and it still works without any problems.
I start to believe the problem comes from `ycm_core.so`, instead of `libclang.dylib`.
I created another test case with `TranslationUnit` in `YouCompleteMe` class.
It still handles the module syntax `@import Foundation;` successfully.
Now I don't really understand why `ycmd` would return `could not build module 'Foundation'`.

```c++
  // ct.m contains "@import Foundation;"
   fs::path test_file = fs::path( "ct.m" );

  vector<string> flags;
  flags.push_back("-fmodules");
  TranslationUnit unit( test_file.string(),
                        std::vector< UnsavedFile >(),
                        flags,
                        clang_index_ );


  std::vector< Diagnostic > diags = unit.Reparse( std::vector< UnsavedFile >());
   // diags is an empty array. No errors are reported.
```

Resolving patch:
diff --git a/ycmd/completers/cpp/flags.py b/ycmd/completers/cpp/flags.py
--- a/ycmd/completers/cpp/flags.py
+++ b/ycmd/completers/cpp/flags.py
@@ -181,8 +181,11 @@ def PrepareFlagsForClang( flags, filename, add_extra_clang_flags = True ):
   flags = _RemoveUnusedFlags( flags, filename )
   if add_extra_clang_flags:
     flags = _EnableTypoCorrection( flags )
-  flags = _SanitizeFlags( flags )
-  return flags
+
+  vector = ycm_core.StringVector()
+  for flag in flags:
+    vector.append( ToCppStringCompatible( flag ) )
+  return vector
 
 
 def _RemoveXclangFlags( flags ):
@@ -205,30 +208,6 @@ def _RemoveXclangFlags( flags ):
   return sanitized_flags
 
 
-def _SanitizeFlags( flags ):
-  """Drops unsafe flags. Currently these are only -arch flags; they tend to
-  crash libclang."""
-
-  sanitized_flags = []
-  saw_arch = False
-  for i, flag in enumerate( flags ):
-    if flag == '-arch':
-      saw_arch = True
-      continue
-    elif flag.startswith( '-arch' ):
-      continue
-    elif saw_arch:
-      saw_arch = False
-      continue
-
-    sanitized_flags.append( flag )
-
-  vector = ycm_core.StringVector()
-  for flag in sanitized_flags:
-    vector.append( ToCppStringCompatible( flag ) )
-  return vector
-
-
 def _RemoveFlagsPrecedingCompiler( flags ):
   """Assuming that the flag just before the first flag (which starts with a
   dash) is the compiler path, removes all flags preceding it."""


Test patch:
diff --git a/ycmd/tests/clang/flags_test.py b/ycmd/tests/clang/flags_test.py
--- a/ycmd/tests/clang/flags_test.py
+++ b/ycmd/tests/clang/flags_test.py
@@ -101,26 +101,6 @@ def FlagsForFile_FlagsCachedWhenDoCacheIsTrue_test( *args ):
     assert_that( flags_list, contains( '-x', 'c' ) )
 
 
-def SanitizeFlags_Passthrough_test():
-  eq_( [ '-foo', '-bar' ],
-       list( flags._SanitizeFlags( [ '-foo', '-bar' ] ) ) )
-
-
-def SanitizeFlags_ArchRemoved_test():
-  expected = [ '-foo', '-bar' ]
-  to_remove = [ '-arch', 'arch_of_evil' ]
-
-  eq_( expected,
-       list( flags._SanitizeFlags( expected + to_remove ) ) )
-
-  eq_( expected,
-       list( flags._SanitizeFlags( to_remove + expected ) ) )
-
-  eq_( expected,
-       list( flags._SanitizeFlags(
-         expected[ :1 ] + to_remove + expected[ -1: ] ) ) )
-
-
 def RemoveUnusedFlags_Passthrough_test():
   eq_( [ '-foo', '-bar' ],
        flags._RemoveUnusedFlags( [ '-foo', '-bar' ], 'file' ) )